public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
Make alias domains selectable in sender acl, a lot of code changes, added challenges for u2f to json_api, added U2F as TFA
badef731
data/web/admin.php | 471 +++---
data/web/edit.php | 15 +-
data/web/img/yubi.ico | Bin 0 -> 1150 bytes
data/web/inc/admin.inc.php | 146 --
data/web/inc/dkim.inc.php | 142 --
data/web/inc/domainadmin.inc.php | 381 -----
data/web/inc/footer.inc.php | 86 ++
data/web/inc/functions.inc.php | 2986 +++++++++++++++++++++++++++++++++++-
data/web/inc/header.inc.php | 4 +-
data/web/inc/init.sql | 15 +-
data/web/inc/lib/U2F.php | 506 ++++++
data/web/inc/lib/Yubico.php | 475 ++++++
data/web/inc/mailbox.inc.php | 1990 ------------------------
data/web/inc/prerequisites.inc.php | 15 +-
data/web/inc/tfa_modals.php | 117 ++
data/web/inc/triggers.inc.php | 116 +-
data/web/inc/vars.inc.php | 8 +-
data/web/js/u2f-api.js | 651 ++++++++
data/web/js/user.js | 4 +-
data/web/json_api.php | 29 +-
data/web/lang/lang.de.php | 24 +-
data/web/lang/lang.en.php | 27 +-
data/web/lang/lang.nl.php | 6 +-
data/web/lang/lang.pt.php | 6 +-
data/web/u2f_api.php | 157 ++
data/web/user.php | 139 +-
26 files changed, 5458 insertions(+), 3058 deletions(-)
create mode 100644 data/web/img/yubi.ico
delete mode 100644 data/web/inc/admin.inc.php
delete mode 100644 data/web/inc/dkim.inc.php
delete mode 100644 data/web/inc/domainadmin.inc.php
create mode 100644 data/web/inc/lib/U2F.php
create mode 100644 data/web/inc/lib/Yubico.php
delete mode 100644 data/web/inc/mailbox.inc.php
create mode 100644 data/web/inc/tfa_modals.php
create mode 100644 data/web/js/u2f-api.js
create mode 100644 data/web/u2f_api.php
Diff
diff --git a/data/web/admin.php b/data/web/admin.php
index c6f78a89..4397a279 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -6,208 +6,192 @@ require_once("inc/header.inc.php");
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
?>
<div class="container">
-<h4><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['admin']['access'];?></h4>
+ <h4><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?=$lang['admin']['access'];?></h4>
-<div class="panel-group" id="accordion_access">
- <div class="panel panel-danger">
- <div class="panel-heading"><?=$lang['admin']['admin_details'];?></div>
- <div class="panel-body">
- <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
- <?php $admindetails = get_admin_details(); ?>
- <input type="hidden" name="admin_user_now" value="<?=htmlspecialchars($admindetails['username']);?>">
- <div class="form-group">
- <label class="control-label col-sm-2" for="admin_user"><?=$lang['admin']['admin'];?>:</label>
- <div class="col-sm-10">
- <input type="text" class="form-control" name="admin_user" id="admin_user" value="<?=htmlspecialchars($admindetails['username']);?>" required>
- ↳ <kbd>a-z A-Z - _ .</kbd>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="admin_pass"><?=$lang['admin']['password'];?>:</label>
- <div class="col-sm-10">
- <input type="password" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>">
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="admin_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
- <div class="col-sm-10">
- <input type="password" class="form-control" name="admin_pass2" id="admin_pass2">
- </div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-2 col-sm-10">
- <button type="submit" name="set_admin_account" class="btn btn-default"><?=$lang['admin']['save'];?></button>
- </div>
- </div>
- </form>
- </div>
- </div>
- <div class="panel panel-default">
- <div style="cursor:pointer;" class="panel-heading" data-toggle="collapse" data-parent="#accordion_access" data-target="#collapseDomAdmins">
- <span class="accordion-toggle"><?=$lang['admin']['domain_admins'];?></span>
- </div>
- <div id="collapseDomAdmins" class="panel-collapse collapse">
- <div class="panel-body">
- <form method="post">
- <div class="table-responsive">
- <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable">
- <thead>
- <tr>
- <th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
- <th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
- <th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
- <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['admin']['action'];?></th>
- </tr>
- </thead>
- <tbody>
- <?php
- foreach (get_domain_admins() as $domain_admin) {
- $da_data = get_domain_admin_details($domain_admin);
- if (!empty($da_data)):
- ?>
- <tr id="data">
- <td><?=htmlspecialchars(strtolower($domain_admin));?></td>
- <td>
- <?php
- foreach ($da_data['selected_domains'] as $domain) {
- echo htmlspecialchars($domain).'<br />';
- }
- ?>
- </td>
- <td><?=$da_data['active'];?></td>
- <td style="text-align: right;">
- <div class="btn-group">
- <a href="edit.php?domainadmin=<?=$domain_admin;?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['admin']['edit'];?></a>
- <a href="delete.php?domainadmin=<?=$domain_admin;?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['admin']['remove'];?></a>
- </div>
- </td>
- </td>
- </tr>
-
- <?php
- else:
- ?>
- <tr id="no-data"><td colspan="4" style="text-align: center; font-style: italic;"><?=$lang['admin']['no_record'];?></td></tr>
- <?php
- endif;
- }
- ?>
- </tbody>
- </table>
- </div>
- </form>
- <small>
- <legend><?=$lang['admin']['add_domain_admin'];?></legend>
- <form class="form-horizontal" role="form" method="post">
- <div class="form-group">
- <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label>
- <div class="col-sm-10">
- <input type="text" class="form-control" name="username" id="username" required>
- ↳ <kbd>a-z A-Z - _ .</kbd>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label>
- <div class="col-sm-10">
- <select title="<?=$lang['admin']['search_domain_da'];?>" style="width:100%" name="domain[]" size="5" multiple>
- <?php
- foreach (mailbox_get_domains() as $domain) {
- echo "<option>".htmlspecialchars($domain)."</option>";
- }
- ?>
- </select>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label>
- <div class="col-sm-10">
- <input type="password" class="form-control" name="password" id="password" placeholder="">
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-2" for="password2"><?=$lang['admin']['password_repeat'];?>:</label>
- <div class="col-sm-10">
- <input type="password" class="form-control" name="password2" id="password2" placeholder="">
- </div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-2 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" name="active" checked> <?=$lang['admin']['active'];?></label>
- </div>
- </div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-2 col-sm-10">
- <button type="submit" name="add_domain_admin" class="btn btn-default"><?=$lang['admin']['add'];?></button>
- </div>
- </div>
- </form>
- </small>
- </div>
- </div>
- </div>
-</div>
-
-<h4><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> <?=$lang['admin']['configuration'];?></h4>
-<div class="panel panel-default">
-<div class="panel-heading"><?=$lang['admin']['dkim_keys'];?></div>
-<div id="collapseDKIM" class="panel-collapse">
-<div class="panel-body">
- <p style="margin-bottom:40px"><?=$lang['admin']['dkim_key_hint'];?></p>
- <?php
- foreach(mailbox_get_domains() as $domain) {
- if (!empty($dkim = dkim_get_key_details($domain))) {
- ?>
- <div class="row">
- <div class="col-xs-3">
- <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br />
- <span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span>
- <span class="label label-info"><?=$dkim['length'];?> bit</span>
- </p>
+ <div class="panel-group" id="accordion_access">
+ <div class="panel panel-danger">
+ <div class="panel-heading"><?=$lang['admin']['admin_details'];?></div>
+ <div class="panel-body">
+ <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+ <?php $admindetails = get_admin_details(); ?>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="admin_user"><?=$lang['admin']['admin'];?>:</label>
+ <div class="col-sm-9">
+ <input type="text" class="form-control" name="admin_user" id="admin_user" value="<?=htmlspecialchars($admindetails['username']);?>" required>
+ ↳ <kbd>a-z A-Z - _ .</kbd>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="admin_pass"><?=$lang['admin']['password'];?>:</label>
+ <div class="col-sm-9">
+ <input type="password" class="form-control" name="admin_pass" id="admin_pass" placeholder="<?=$lang['admin']['unchanged_if_empty'];?>">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="admin_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
+ <div class="col-sm-9">
+ <input type="password" class="form-control" name="admin_pass2" id="admin_pass2">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <button type="submit" name="edit_admin_account" class="btn btn-default"><?=$lang['admin']['save'];?></button>
+ </div>
+ </div>
+ </form>
+ <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><?=get_tfa()['pretty'];?></p>
+ </div>
</div>
- <div class="col-xs-8">
- <pre><?=$dkim['dkim_txt'];?></pre>
+ <div class="row">
+ <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?>:</div>
+ <div class="col-md-9 col-xs-7">
+ <select data-width="auto" 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="none"><?=$lang['tfa']['none'];?></option>
+ </select>
+ </div>
</div>
- <div class="col-xs-1">
- <form class="form-inline" method="post">
- <input type="hidden" name="domain" value="<?=$domain;?>">
- <input type="hidden" name="dkim_delete_key" value="1">
- <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
+ </div>
+ </div>
+ <div class="panel panel-default">
+ <div style="cursor:pointer;" class="panel-heading" data-toggle="collapse" data-parent="#accordion_access" data-target="#collapseDomAdmins">
+ <span class="accordion-toggle"><?=$lang['admin']['domain_admins'];?></span>
+ </div>
+ <div id="collapseDomAdmins" class="panel-collapse collapse">
+ <div class="panel-body">
+ <form method="post">
+ <div class="table-responsive">
+ <table class="table table-striped sortable-theme-bootstrap" data-sortable id="domainadminstable">
+ <thead>
+ <tr>
+ <th class="sort-table" style="min-width: 100px;"><?=$lang['admin']['username'];?></th>
+ <th class="sort-table" style="min-width: 166px;"><?=$lang['admin']['admin_domains'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['admin']['active'];?></th>
+ <th class="sort-table" style="min-width: 76px;"><?=$lang['tfa']['tfa'];?></th>
+ <th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['admin']['action'];?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ foreach (get_domain_admins() as $domain_admin) {
+ $da_data = get_domain_admin_details($domain_admin);
+ if (!empty($da_data)):
+ ?>
+ <tr id="data">
+ <td><?=htmlspecialchars(strtolower($domain_admin));?></td>
+ <td>
+ <?php
+ foreach ($da_data['selected_domains'] as $domain) {
+ echo htmlspecialchars($domain).'<br />';
+ }
+ ?>
+ </td>
+ <td><?=$da_data['active'];?></td>
+ <td><?=empty($da_data['tfa_active_int']) ? "✘" : "✔";?></td>
+ <td style="text-align: right;">
+ <div class="btn-group">
+ <a href="edit.php?domainadmin=<?=$domain_admin;?>" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['admin']['edit'];?></a>
+ <a href="delete.php?domainadmin=<?=$domain_admin;?>" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['admin']['remove'];?></a>
+ </div>
+ </td>
+ </td>
+ </tr>
+
+ <?php
+ else:
+ ?>
+ <tr id="no-data"><td colspan="4" style="text-align: center; font-style: italic;"><?=$lang['admin']['no_record'];?></td></tr>
+ <?php
+ endif;
+ }
+ ?>
+ </tbody>
+ </table>
+ </div>
</form>
+ <small>
+ <legend><?=$lang['admin']['add_domain_admin'];?></legend>
+ <form class="form-horizontal" role="form" method="post">
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="username"><?=$lang['admin']['username'];?>:</label>
+ <div class="col-sm-10">
+ <input type="text" class="form-control" name="username" id="username" required>
+ ↳ <kbd>a-z A-Z - _ .</kbd>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="name"><?=$lang['admin']['admin_domains'];?>:</label>
+ <div class="col-sm-10">
+ <select title="<?=$lang['admin']['search_domain_da'];?>" style="width:100%" name="domain[]" size="5" multiple>
+ <?php
+ foreach (mailbox_get_domains() as $domain) {
+ echo "<option>".htmlspecialchars($domain)."</option>";
+ }
+ ?>
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password"><?=$lang['admin']['password'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password" id="password" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-2" for="password2"><?=$lang['admin']['password_repeat'];?>:</label>
+ <div class="col-sm-10">
+ <input type="password" class="form-control" name="password2" id="password2" placeholder="">
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <div class="checkbox">
+ <label><input type="checkbox" name="active" checked> <?=$lang['admin']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="add_domain_admin" class="btn btn-default"><?=$lang['admin']['add'];?></button>
+ </div>
+ </div>
+ </form>
+ </small>
</div>
</div>
- <?php
- }
- else {
- ?>
- <div class="row">
- <div class="col-xs-3">
- <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
- </div>
- <div class="col-xs-8"><pre>-</pre></div>
- <div class="col-xs-1"> </div>
</div>
+ </div>
+
+ <h4><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> <?=$lang['admin']['configuration'];?></h4>
+ <div class="panel panel-default">
+ <div class="panel-heading"><?=$lang['admin']['dkim_keys'];?></div>
+ <div id="collapseDKIM" class="panel-collapse">
+ <div class="panel-body">
+ <p style="margin-bottom:40px"><?=$lang['admin']['dkim_key_hint'];?></p>
<?php
- }
- foreach(mailbox_get_alias_domains($domain) as $alias_domain) {
- if (!empty($dkim = dkim_get_key_details($alias_domain))) {
+ foreach(mailbox_get_domains() as $domain) {
+ if (!empty($dkim = dkim_get_key_details($domain))) {
?>
<div class="row">
- <div class="col-xs-offset-1 col-xs-2">
- <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small>
+ <div class="col-xs-3">
+ <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br />
<span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span>
<span class="label label-info"><?=$dkim['length'];?> bit</span>
- </p>
+ </p>
</div>
<div class="col-xs-8">
- <pre><?=$dkim['dkim_txt'];?></pre>
+ <pre><?=$dkim['dkim_txt'];?></pre>
</div>
<div class="col-xs-1">
<form class="form-inline" method="post">
- <input type="hidden" name="domain" value="<?=$alias_domain;?>">
+ <input type="hidden" name="domain" value="<?=$domain;?>">
<input type="hidden" name="dkim_delete_key" value="1">
- <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
+ <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
</form>
</div>
</div>
@@ -216,57 +200,90 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
else {
?>
<div class="row">
- <div class="col-xs-2 col-xs-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 class="col-xs-3">
+ <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
</div>
- <div class="col-xs-8"><pre>-</pre></div>
- <div class="col-xs-1"> </div>
+ <div class="col-xs-8"><pre>-</pre></div>
+ <div class="col-xs-1"> </div>
</div>
<?php
}
- }
- }
- foreach(dkim_get_blind_keys() as $blind) {
- if (!empty($dkim = dkim_get_key_details($blind))) {
- ?>
- <div class="row">
- <div class="col-xs-3">
- <p>Domain: <strong><?=htmlspecialchars($blind);?></strong><br /><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
- </div>
- <div class="col-xs-8">
- <pre><?=$dkim['dkim_txt'];?></pre>
+ foreach(mailbox_get_alias_domains($domain) as $alias_domain) {
+ if (!empty($dkim = dkim_get_key_details($alias_domain))) {
+ ?>
+ <div class="row">
+ <div class="col-xs-offset-1 col-xs-2">
+ <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small>
+ <span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span>
+ <span class="label label-info"><?=$dkim['length'];?> bit</span>
+ </p>
+ </div>
+ <div class="col-xs-8">
+ <pre><?=$dkim['dkim_txt'];?></pre>
+ </div>
+ <div class="col-xs-1">
+ <form class="form-inline" method="post">
+ <input type="hidden" name="domain" value="<?=$alias_domain;?>">
+ <input type="hidden" name="dkim_delete_key" value="1">
+ <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
+ </form>
+ </div>
</div>
- <div class="col-xs-1">
- <form class="form-inline" method="post">
- <input type="hidden" name="domain" value="<?=$blind;?>">
- <input type="hidden" name="dkim_delete_key" value="1">
- <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
- </form>
+ <?php
+ }
+ else {
+ ?>
+ <div class="row">
+ <div class="col-xs-2 col-xs-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>
- <?php
+ <div class="col-xs-8"><pre>-</pre></div>
+ <div class="col-xs-1"> </div>
+ </div>
+ <?php
+ }
+ }
+ }
+ foreach(dkim_get_blind_keys() as $blind) {
+ if (!empty($dkim = dkim_get_key_details($blind))) {
+ ?>
+ <div class="row">
+ <div class="col-xs-3">
+ <p>Domain: <strong><?=htmlspecialchars($blind);?></strong><br /><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
+ </div>
+ <div class="col-xs-8">
+ <pre><?=$dkim['dkim_txt'];?></pre>
+ </div>
+ <div class="col-xs-1">
+ <form class="form-inline" method="post">
+ <input type="hidden" name="domain" value="<?=$blind;?>">
+ <input type="hidden" name="dkim_delete_key" value="1">
+ <a href="#" onclick="$(this).closest('form').submit()" data-toggle="tooltip" data-placement="top" title="<?=$lang['user']['delete_now'];?>"><span class="glyphicon glyphicon-remove"></span></a>
+ </form>
+ </div>
+ </div>
+ <?php
+ }
}
- }
- ?>
- <legend style="margin-top:40px"><?=$lang['admin']['dkim_add_key'];?></legend>
- <form class="form-inline" role="form" method="post">
- <div class="form-group">
- <label for="domain">Domain</label>
- <input class="form-control" id="domain" name="domain" placeholder="example.org" required>
- </div>
- <div class="form-group">
- <select data-width="200px" 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 type="submit" name="dkim_add_key" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
- </form>
-</div>
-</div>
-</div>
+ ?>
+ <legend style="margin-top:40px"><?=$lang['admin']['dkim_add_key'];?></legend>
+ <form class="form-inline" role="form" method="post">
+ <div class="form-group">
+ <label for="domain">Domain</label>
+ <input class="form-control" id="domain" name="domain" placeholder="example.org" required>
+ </div>
+ <div class="form-group">
+ <select data-width="200px" 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 type="submit" name="dkim_add_key" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
+ </form>
+ </div>
+ </div>
+ </div>
</div> <!-- /container -->
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js" integrity="sha384-YWP9O4NjmcGo4oEJFXvvYSEzuHIvey+LbXkBNJ1Kd0yfugEZN9NCQNpRYBVC1RvA" crossorigin="anonymous"></script>
<script src="js/sorttable.js"></script>
<script src="js/admin.js"></script>
diff --git a/data/web/edit.php b/data/web/edit.php
index ce54a2af..fcdb5734 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -66,11 +66,11 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
<h4><?=$lang['edit']['domain_admin'];?></h4>
<br />
<form class="form-horizontal" role="form" method="post" action="<?=($FORM_ACTION == "previous") ? $_SESSION['return_to'] : null;?>">
- <input type="hidden" name="username" value="<?=htmlspecialchars($domain_admin);?>">
+ <input type="hidden" name="username_now" value="<?=htmlspecialchars($domain_admin);?>">
<div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
- <input class="form-control" type="text" disabled value="<?=htmlspecialchars($domain_admin);?>" />
+ <input class="form-control" type="text" name="username" value="<?=htmlspecialchars($domain_admin);?>" />
</div>
</div>
<div class="form-group">
@@ -113,7 +113,14 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
- <button type="submit" name="trigger_edit_domain_admin" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ <div class="checkbox">
+ <label><input type="checkbox" name="delete_tfa"> <?=$lang['tfa']['delete_tfa'];?></label>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-2 col-sm-10">
+ <button type="submit" name="edit_domain_admin" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
@@ -575,7 +582,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] ==
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
- <button type="submit" name="trigger_edit_syncjob" value="1" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
+ <button type="submit" name="edit_syncjob" value="1" class="btn btn-success btn-sm"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
diff --git a/data/web/img/yubi.ico b/data/web/img/yubi.ico
new file mode 100644
index 00000000..126c2b17
Binary files /dev/null and b/data/web/img/yubi.ico differ
diff --git a/data/web/inc/admin.inc.php b/data/web/inc/admin.inc.php
deleted file mode 100644
index d77d225d..00000000
--- a/data/web/inc/admin.inc.php
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-function get_admin_details() {
- // No parameter to be given, only one admin should exist
- global $pdo;
- global $lang;
- $data = array();
- if ($_SESSION['mailcow_cc_role'] != 'admin') {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin`WHERE `superadmin`='1' AND active='1'");
- $stmt->execute();
- $data = $stmt->fetch(PDO::FETCH_ASSOC);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- }
- return $data;
-}
-function edit_admin($postarray) {
- global $lang;
- global $pdo;
- $username = $postarray['username'];
- $password = $postarray['password'];
- $password2 = $postarray['password2'];
- isset($postarray['active']) ? $active = '1' : $active = '0';
-
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- if(isset($postarray['domain'])) {
- foreach ($postarray['domain'] as $domain) {
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- }
- }
-
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if(isset($postarray['domain'])) {
- foreach ($postarray['domain'] as $domain) {
- try {
- $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
- VALUES (:username, :domain, :created, :active)");
- $stmt->execute(array(
- ':username' => $username,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- }
-
- if (!empty($password) && !empty($password2)) {
- if ($password != $password2) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_mismatch'])
- );
- return false;
- }
- $password_hashed = hash_password($password);
- try {
- $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
- $stmt->execute(array(
- ':password_hashed' => $password_hashed,
- ':username' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- else {
- try {
- $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `active` = :active WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
- );
-}
\ No newline at end of file
diff --git a/data/web/inc/dkim.inc.php b/data/web/inc/dkim.inc.php
deleted file mode 100644
index 63c72119..00000000
--- a/data/web/inc/dkim.inc.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-function dkim_add_key($postarray) {
- global $lang;
- global $pdo;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- // $_SESSION['return'] = array(
- // 'type' => 'danger',
- // 'msg' => sprintf($lang['danger']['access_denied'])
- // );
- // return false;
- // }
- $key_length = intval($postarray['key_size']);
- $domain = $postarray['domain'];
- if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
- );
- return false;
- }
-
- if (!empty(glob($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
- );
- return false;
- }
-
- $config = array(
- "digest_alg" => "sha256",
- "private_key_bits" => $key_length,
- "private_key_type" => OPENSSL_KEYTYPE_RSA,
- );
- if ($keypair_ressource = openssl_pkey_new($config)) {
- $key_details = openssl_pkey_get_details($keypair_ressource);
- $pubKey = implode(array_slice(
- array_filter(
- explode(PHP_EOL, $key_details['key'])
- ), 1, -1)
- );
- // Save public key to file
- file_put_contents($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim', $pubKey);
- // Save private key to file
- openssl_pkey_export_to_file($keypair_ressource, $GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim');
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['dkim_added'])
- );
- return true;
- }
- else {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
- );
- return false;
- }
-}
-function dkim_get_key_details($domain) {
- $data = array();
- if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $dkim_pubkey_file = escapeshellarg($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
- if (file_exists(substr($dkim_pubkey_file, 1, -1))) {
- $data['pubkey'] = file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
- $data['length'] = (strlen($data['pubkey']) < 391) ? 1024 : 2048;
- $data['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
- }
- }
- return $data;
-}
-function dkim_get_blind_keys() {
- global $lang;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- $domains = array();
- $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]);
- $dnstxt_files = array_diff($dnstxt_folder, array('.', '..'));
- foreach($dnstxt_files as $file) {
- $domains[] = substr($file, 0, -5);
- }
- return array_diff($domains, array_merge(mailbox_get_domains(), mailbox_get_alias_domains()));
-}
-function dkim_delete_key($postarray) {
- global $lang;
- $domain = $postarray['domain'];
-
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- // $_SESSION['return'] = array(
- // 'type' => 'danger',
- // 'msg' => sprintf($lang['danger']['access_denied'])
- // );
- // return false;
- // }
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
- );
- return false;
- }
- exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'), $out, $return);
- if ($return != "0") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
- );
- return false;
- }
- exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim'), $out, $return);
- if ($return != "0") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['dkim_removed'])
- );
- return true;
-}
\ No newline at end of file
diff --git a/data/web/inc/domainadmin.inc.php b/data/web/inc/domainadmin.inc.php
deleted file mode 100644
index 2e122553..00000000
--- a/data/web/inc/domainadmin.inc.php
+++ /dev/null
@@ -1,381 +0,0 @@
-<?php
-function add_domain_admin($postarray) {
- global $lang;
- global $pdo;
- $username = strtolower(trim($postarray['username']));
- $password = $postarray['password'];
- $password2 = $postarray['password2'];
- isset($postarray['active']) ? $active = '1' : $active = '0';
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (empty($postarray['domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
- WHERE `username` = :username");
- $stmt->execute(array(':username' => $username));
- $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
-
- $stmt = $pdo->prepare("SELECT `username` FROM `admin`
- WHERE `username` = :username");
- $stmt->execute(array(':username' => $username));
- $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
-
- $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
- WHERE `username` = :username");
- $stmt->execute(array(':username' => $username));
- $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- foreach ($num_results as $num_results_each) {
- if ($num_results_each != 0) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
- );
- return false;
- }
- }
- if (!empty($password) && !empty($password2)) {
- if ($password != $password2) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_mismatch'])
- );
- return false;
- }
- $password_hashed = hash_password($password);
- foreach ($postarray['domain'] as $domain) {
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
- VALUES (:username, :domain, :created, :active)");
- $stmt->execute(array(
- ':username' => $username,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- delete_domain_admin(array('username' => $username));
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- try {
- $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`)
- VALUES (:username, :password_hashed, '0', :created, :modified, :active)");
- $stmt->execute(array(
- ':username' => $username,
- ':password_hashed' => $password_hashed,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- else {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_empty'])
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username))
- );
-}
-function delete_domain_admin($postarray) {
- global $pdo;
- global $lang;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- $username = $postarray['username'];
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_admin_removed'], htmlspecialchars($username))
- );
-}
-function get_domain_admins() {
- global $pdo;
- global $lang;
- $domainadmins = array();
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- try {
- $stmt = $pdo->query("SELECT DISTINCT
- `username`
- FROM `domain_admins`
- WHERE `username` IN (
- SELECT `username` FROM `admin`
- WHERE `superadmin`!='1'
- )");
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while ($row = array_shift($rows)) {
- $domainadmins[] = $row['username'];
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- }
- return $domainadmins;
-}
-function get_domain_admin_details($domain_admin) {
- global $pdo;
- global $lang;
- $domainadmindata = array();
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $domain_admin))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("SELECT
- `created`,
- `active` AS `active_int`,
- CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
- FROM `domain_admins`
- WHERE `username`= :domain_admin");
- $stmt->execute(array(
- ':domain_admin' => $domain_admin
- ));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- $domainadmindata['active'] = $row['active'];
- $domainadmindata['active_int'] = $row['active_int'];
- $domainadmindata['created'] = $row['created'];
- // GET SELECTED
- $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
- WHERE `domain` IN (
- SELECT `domain` FROM `domain_admins`
- WHERE `username`= :domain_admin)");
- $stmt->execute(array(':domain_admin' => $domain_admin));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $domainadmindata['selected_domains'][] = $row['domain'];
- }
- // GET UNSELECTED
- $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
- WHERE `domain` NOT IN (
- SELECT `domain` FROM `domain_admins`
- WHERE `username`= :domain_admin)");
- $stmt->execute(array(':domain_admin' => $domain_admin));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $domainadmindata['unselected_domains'][] = $row['domain'];
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- }
- return $domainadmindata;
-}
-function edit_domain_admin($postarray) {
- global $lang;
- global $pdo;
- $username = $postarray['username'];
- $password = $postarray['password'];
- $password2 = $postarray['password2'];
- isset($postarray['active']) ? $active = '1' : $active = '0';
-
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- if(isset($postarray['domain'])) {
- foreach ($postarray['domain'] as $domain) {
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- }
- }
-
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if(isset($postarray['domain'])) {
- foreach ($postarray['domain'] as $domain) {
- try {
- $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
- VALUES (:username, :domain, :created, :active)");
- $stmt->execute(array(
- ':username' => $username,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- }
-
- if (!empty($password) && !empty($password2)) {
- if ($password != $password2) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_mismatch'])
- );
- return false;
- }
- $password_hashed = hash_password($password);
- try {
- $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
- $stmt->execute(array(
- ':password_hashed' => $password_hashed,
- ':username' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- else {
- try {
- $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `active` = :active WHERE `username` = :username");
- $stmt->execute(array(
- ':username' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
- );
-}
\ No newline at end of file
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
index 642242a4..d250fc0b 100644
--- a/data/web/inc/footer.inc.php
+++ b/data/web/inc/footer.inc.php
@@ -1,4 +1,5 @@
<?php
+include("inc/tfa_modals.php");
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin"):
?>
<div id="RestartSOGo" class="modal fade" role="dialog">
@@ -26,6 +27,7 @@ endif;
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/7.0.2/bootstrap-slider.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.4/js/bootstrap-select.js"></script>
+<script src="/js/u2f-api.js"></script>
<script>
// Select language and reopen active URL without POST
function setLang(sel) {
@@ -34,6 +36,90 @@ function setLang(sel) {
}
$(document).ready(function() {
+ // Confirm TFA modal
+ <?php if (isset($_SESSION['pending_tfa_method'])):?>
+ $('#ConfirmTFAModal').modal({
+ backdrop: 'static',
+ keyboard: false
+ });
+ $('#ConfirmTFAModal').on('shown.bs.modal', function(){
+ $(this).find('#token').focus();
+ // If U2F
+ if(document.getElementById("u2f_auth_data") !== null) {
+ $.ajax({
+ type: "GET",
+ cache: false,
+ dataType: 'script',
+ url: "json_api.php",
+ data: {
+ 'action':'get_u2f_auth_challenge',
+ 'object':'<?=(isset($_SESSION['pending_mailcow_cc_username'])) ? $_SESSION['pending_mailcow_cc_username'] : null;?>',
+ },
+ success: function(data){
+ data;
+ }
+ });
+ setTimeout(function() {
+ console.log("sign: ", req);
+ u2f.sign(req, function(data) {
+ var form = document.getElementById('u2f_auth_form');
+ var auth = document.getElementById('u2f_auth_data');
+ console.log("Authenticate callback", data);
+ auth.value = JSON.stringify(data);
+ form.submit();
+ });
+ }, 1000);
+ }
+ });
+ <?php endif; ?>
+
+ // Set TFA modals
+ $('#selectTFA').change(function () {
+ if ($(this).val() == "yubi_otp") {
+ $('#YubiOTPModal').modal('show');
+ $("option:selected").prop("selected", false);
+ }
+ if ($(this).val() == "u2f") {
+ $('#U2FModal').modal('show');
+ $("option:selected").prop("selected", false);
+ $.ajax({
+ type: "GET",
+ cache: false,
+ dataType: 'script',
+ url: "json_api.php",
+ data: {
+ 'action':'get_u2f_reg_challenge',
+ 'object':'<?=(isset($_SESSION['mailcow_cc_username'])) ? $_SESSION['mailcow_cc_username'] : null;?>',
+ },
+ success: function(data){
+ data;
+ }
+ });
+ setTimeout(function() {
+ console.log("Register: ", req);
+ u2f.register([req], sigs, function(data) {
+ var form = document.getElementById('u2f_reg_form');
+ var reg = document.getElementById('u2f_register_data');
+ console.log("Register callback", data);
+ if (data.errorCode && data.errorCode != 0) {
+ var u2f_return_code = document.getElementById('u2f_return_code');
+ u2f_return_code.style.display = u2f_return_code.style.display === 'none' ? '' : null;
+ if (data.errorCode == "4") { data.errorCode = "4 - The presented device is not eligible for this request. For a registration request this may mean that the token is already registered, and for a sign request it may mean that the token does not know the presented key handle"; }
+ u2f_return_code.innerHTML = 'Error code: ' + data.errorCode;
+ return;
+ }
+ reg.value = JSON.stringify(data);
+ form.submit();
+ });
+ }, 1000);
+ }
+ if ($(this).val() == "none") {
+ $('#DisableTFAModal').modal('show');
+ $("option:selected").prop("selected", false);
+ }
+ });
+
+ // Activate tooltips
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 4e591d2a..9ec3271e 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -1,8 +1,4 @@
<?php
-require_once 'dkim.inc.php';
-require_once 'mailbox.inc.php';
-require_once 'domainadmin.inc.php';
-require_once 'admin.inc.php';
function hash_password($password) {
$salt_str = bin2hex(openssl_random_pseudo_bytes(8));
return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
@@ -69,7 +65,7 @@ function hasMailboxObjectAccess($username, $role, $object) {
function init_db_schema() {
global $pdo;
try {
- $stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`");
+ $stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`, `tfa`");
$stmt->execute();
}
catch (Exception $e) {
@@ -171,9 +167,18 @@ function check_login($user, $pass) {
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
- if (verify_ssha256($row['password'], $pass) !== false) {
- unset($_SESSION['ldelay']);
- return "admin";
+ if (verify_ssha256($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']);
+ return "pending";
+ }
+ else {
+ unset($_SESSION['ldelay']);
+ return "admin";
+ }
}
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
@@ -184,8 +189,17 @@ function check_login($user, $pass) {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_ssha256($row['password'], $pass) !== false) {
- unset($_SESSION['ldelay']);
- return "domainadmin";
+ 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']);
+ return "pending";
+ }
+ else {
+ unset($_SESSION['ldelay']);
+ return "domainadmin";
+ }
}
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
@@ -218,7 +232,7 @@ function formatBytes($size, $precision = 2) {
}
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
-function set_admin_account($postarray) {
+function edit_admin_account($postarray) {
global $lang;
global $pdo;
if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -228,23 +242,17 @@ function set_admin_account($postarray) {
);
return false;
}
- $name = $postarray['admin_user'];
- $name_now = $postarray['admin_user_now'];
+ $username = $postarray['admin_user'];
+ $username_now = $_SESSION['mailcow_cc_username'];
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $name)) || empty ($name)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
- if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $name_now)) || empty ($name_now)) {
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['username_invalid'])
);
return false;
}
+
if (!empty($postarray['admin_pass']) && !empty($postarray['admin_pass2'])) {
if ($postarray['admin_pass'] != $postarray['admin_pass2']) {
$_SESSION['return'] = array(
@@ -258,13 +266,13 @@ function set_admin_account($postarray) {
$stmt = $pdo->prepare("UPDATE `admin` SET
`modified` = :modified,
`password` = :password_hashed,
- `username` = :name
- WHERE `username` = :username");
+ `username` = :username1
+ WHERE `username` = :username2");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':modified' => date('Y-m-d H:i:s'),
- ':name' => $name,
- ':username' => $name_now
+ ':username1' => $username,
+ ':username2' => $username_now
));
}
catch (PDOException $e) {
@@ -279,12 +287,12 @@ function set_admin_account($postarray) {
try {
$stmt = $pdo->prepare("UPDATE `admin` SET
`modified` = :modified,
- `username` = :name
- WHERE `username` = :name_now");
+ `username` = :username1
+ WHERE `username` = :username2");
$stmt->execute(array(
- ':name' => $name,
+ ':username1' => $username,
':modified' => date('Y-m-d H:i:s'),
- ':name_now' => $name_now
+ ':username2' => $username_now
));
}
catch (PDOException $e) {
@@ -296,15 +304,10 @@ function set_admin_account($postarray) {
}
}
try {
- $stmt = $pdo->prepare("UPDATE `domain_admins` SET
- `domain` = :domain,
- `username` = :name
- WHERE `username` = :name_now");
- $stmt->execute(array(
- ':domain' => 'ALL',
- ':name' => $name,
- ':name_now' => $name_now
- ));
+ $stmt = $pdo->prepare("UPDATE `domain_admins` SET `domain` = 'ALL', `username` = :username1 WHERE `username` = :username2");
+ $stmt->execute(array(':username1' => $username, ':username2' => $username_now));
+ $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username1 WHERE `username` = :username2");
+ $stmt->execute(array(':username1' => $username, ':username2' => $username_now));
}
catch (PDOException $e) {
$_SESSION['return'] = array(
@@ -313,6 +316,7 @@ function set_admin_account($postarray) {
);
return false;
}
+ $_SESSION['mailcow_cc_username'] = $username;
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['admin_modified'])
@@ -354,7 +358,7 @@ function set_time_limited_aliases($postarray) {
return false;
}
- switch ($postarray["trigger_set_time_limited_aliases"]) {
+ switch ($postarray["set_time_limited_aliases"]) {
case "generate":
if (!is_numeric($postarray["validity"]) || $postarray["validity"] > 672) {
$_SESSION['return'] = array(
@@ -544,20 +548,23 @@ function edit_user_account($postarray) {
$username = $_SESSION['mailcow_cc_username'];
}
$password_old = $postarray['user_old_pass'];
- isset($postarray['togglePwNew']) ? $pwnew_active = '1' : $pwnew_active = '0';
- if (isset($pwnew_active) && $pwnew_active == "1") {
+ if (isset($postarray['user_new_pass']) && isset($postarray['user_new_pass2'])) {
$password_new = $postarray['user_new_pass'];
$password_new2 = $postarray['user_new_pass2'];
}
- if (!check_login($username, $password_old) == "user") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
+ $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+ WHERE `username` = :user");
+ $stmt->execute(array(':user' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!verify_ssha256($row['password'], $password_old)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
if (isset($password_new) && isset($password_new2)) {
if (!empty($password_new2) && !empty($password_new)) {
@@ -597,7 +604,7 @@ function edit_user_account($postarray) {
}
$_SESSION['return'] = array(
'type' => 'success',
- 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
+ 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username))
);
}
function get_spam_score($username = null) {
@@ -1493,4 +1500,2885 @@ function is_valid_domain_name($domain_name) {
&& preg_match("/^.{1,253}$/", $domain_name)
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
}
+function add_domain_admin($postarray) {
+ global $lang;
+ global $pdo;
+ $username = strtolower(trim($postarray['username']));
+ $password = $postarray['password'];
+ $password2 = $postarray['password2'];
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (empty($postarray['domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+ $stmt = $pdo->prepare("SELECT `username` FROM `admin`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+ $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ foreach ($num_results as $num_results_each) {
+ if ($num_results_each != 0) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
+ );
+ return false;
+ }
+ }
+ if (!empty($password) && !empty($password2)) {
+ if ($password != $password2) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_mismatch'])
+ );
+ return false;
+ }
+ $password_hashed = hash_password($password);
+ foreach ($postarray['domain'] as $domain) {
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
+ VALUES (:username, :domain, :created, :active)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ catch (PDOException $e) {
+ delete_domain_admin(array('username' => $username));
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`)
+ VALUES (:username, :password_hashed, '0', :created, :modified, :active)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':password_hashed' => $password_hashed,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ else {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_empty'])
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username))
+ );
+}
+function delete_domain_admin($postarray) {
+ global $pdo;
+ global $lang;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ $username = $postarray['username'];
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username,
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_admin_removed'], htmlspecialchars($username))
+ );
+}
+function get_domain_admins() {
+ global $pdo;
+ global $lang;
+ $domainadmins = array();
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->query("SELECT DISTINCT
+ `username`
+ FROM `domain_admins`
+ WHERE `username` IN (
+ SELECT `username` FROM `admin`
+ WHERE `superadmin`!='1'
+ )");
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while ($row = array_shift($rows)) {
+ $domainadmins[] = $row['username'];
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ return $domainadmins;
+}
+function get_domain_admin_details($domain_admin) {
+ global $pdo;
+ global $lang;
+ $domainadmindata = array();
+ if (isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (!isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ (!isset($domain_admin)) ? $domain_admin = $_SESSION['mailcow_cc_username'] : null;
+
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $domain_admin))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `tfa`.`active` AS `tfa_active_int`,
+ `domain_admins`.`username`,
+ `domain_admins`.`created`,
+ `domain_admins`.`active` AS `active_int`,
+ CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+ FROM `domain_admins`
+ LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
+ WHERE `domain_admins`.`username`= :domain_admin");
+ $stmt->execute(array(
+ ':domain_admin' => $domain_admin
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $domainadmindata['username'] = $row['username'];
+ $domainadmindata['active'] = $row['active'];
+ $domainadmindata['active_int'] = $row['active_int'];
+ $domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
+ $domainadmindata['created'] = $row['created'];
+ // GET SELECTED
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
+ WHERE `domain` IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :domain_admin)");
+ $stmt->execute(array(':domain_admin' => $domain_admin));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $domainadmindata['selected_domains'][] = $row['domain'];
+ }
+ // GET UNSELECTED
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
+ WHERE `domain` NOT IN (
+ SELECT `domain` FROM `domain_admins`
+ WHERE `username`= :domain_admin)");
+ $stmt->execute(array(':domain_admin' => $domain_admin));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $domainadmindata['unselected_domains'][] = $row['domain'];
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ return $domainadmindata;
+}
+function set_tfa($postarray) {
+ global $lang;
+ global $pdo;
+ global $yubi;
+ global $u2f;
+
+ if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
+ $_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ $username = $_SESSION['mailcow_cc_username'];
+
+ $stmt = $pdo->prepare("SELECT `password` FROM `admin`
+ WHERE `username` = :user");
+ $stmt->execute(array(':user' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!verify_ssha256($row['password'], $postarray["confirm_password"])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ switch ($postarray["tfa_method"]) {
+ case "yubi_otp":
+ if (!ctype_alnum($postarray["otp_token"]) || strlen($postarray["otp_token"]) != 44) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['tfa_token_invalid'])
+ );
+ return false;
+ }
+ $yauth = $yubi->verify($postarray["otp_token"]);
+ if (PEAR::isError($yauth)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'Yubico Authentication error: ' . $yauth->getMessage()
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `active`) VALUES
+ (:username, 'yubi_otp', 1)");
+ $stmt->execute(array(':username' => $username));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username))
+ );
+ break;
+
+ case "u2f":
+ try {
+ $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token']));
+ $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) values (?, 'u2f', ?, ?, ?, ?)");
+ $stmt->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['object_modified'], $username)
+ );
+ $_SESSION['regReq'] = null;
+ }
+ catch (Exception $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => "U2F: " . $e->getMessage()
+ );
+ $_SESSION['regReq'] = null;
+ }
+ 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',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username))
+ );
+ break;
+ }
+}
+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 `authmech` FROM `tfa`
+ WHERE `username` = :username");
+ $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";
+ return $data;
+ break;
+ case "u2f":
+ $data['name'] = "u2f";
+ $data['pretty'] = "Fido U2F";
+ 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";
+ return $data;
+ break;
+ default:
+ $data['name'] = 'none';
+ $data['pretty'] = "-";
+ return $data;
+ break;
+ }
+}
+function verify_tfa_login($username, $token) {
+ global $pdo;
+ global $lang;
+ global $yubi;
+
+ $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ switch ($row["authmech"]) {
+ case "yubi_otp":
+ if (!ctype_alnum($token) || strlen($token) != 44) {
+ return false;
+ }
+ $yauth = $yubi->verify($token);
+ if (PEAR::isError($yauth)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'Yubico Authentication error: ' . $yauth->getMessage()
+ );
+ return false;
+ }
+ else {
+ return true;
+ }
+ return false;
+ break;
+ case "u2f":
+ try {
+ global $u2f;
+ $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
+ $stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?");
+ $stmt->execute(array($reg->counter, $reg->id));
+ $_SESSION['authReq'] = null;
+ return true;
+ }
+ catch (Exception $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => "U2F: " . $e->getMessage()
+ );
+ $_SESSION['regReq'] = null;
+ return false;
+ }
+ return false;
+ break;
+ case "hotp":
+ return false;
+ break;
+ case "totp":
+ return false;
+ break;
+ default:
+ return false;
+ break;
+ }
+ return false;
+}
+function edit_domain_admin($postarray) {
+ global $lang;
+ global $pdo;
+
+ if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ // Administrator
+ if ($_SESSION['mailcow_cc_role'] == "admin") {
+ $username = $postarray['username'];
+ $username_now = $postarray['username_now'];
+ $password = $postarray['password'];
+ $password2 = $postarray['password2'];
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+
+ if(isset($postarray['domain'])) {
+ foreach ($postarray['domain'] as $domain) {
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ }
+ }
+
+ if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ if ($username != $username_now) {
+ if (empty(get_domain_admin_details($username_now)['username']) || !empty(get_domain_admin_details($username)['username'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username_now,
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if(isset($postarray['domain'])) {
+ foreach ($postarray['domain'] as $domain) {
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
+ VALUES (:username, :domain, :created, :active)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ }
+
+ if (!empty($password) && !empty($password2)) {
+ if ($password != $password2) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_mismatch'])
+ );
+ return false;
+ }
+ $password_hashed = hash_password($password);
+ try {
+ $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username1, `modified` = :modified, `active` = :active, `password` = :password_hashed WHERE `username` = :username2");
+ $stmt->execute(array(
+ ':password_hashed' => $password_hashed,
+ ':username1' => $username,
+ ':username2' => $username_now,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ if (isset($postarray['delete_tfa'])) {
+ $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username_now));
+ }
+ else {
+ $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username WHERE `username` = :username_now");
+ $stmt->execute(array(':username' => $username, ':username_now' => $username_now));
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ else {
+ try {
+ $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username1, `modified` = :modified, `active` = :active WHERE `username` = :username2");
+ $stmt->execute(array(
+ ':username1' => $username,
+ ':username2' => $username_now,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ if (isset($postarray['delete_tfa'])) {
+ $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ }
+ else {
+ $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username WHERE `username` = :username_now");
+ $stmt->execute(array(':username' => $username, ':username_now' => $username_now));
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
+ );
+ }
+ // Domain administrator
+ // Can only edit itself
+ elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
+ $username = $_SESSION['mailcow_cc_username'];
+ $password_old = $postarray['user_old_pass'];
+ $password_new = $postarray['user_new_pass'];
+ $password_new2 = $postarray['user_new_pass2'];
+
+ $stmt = $pdo->prepare("SELECT `password` FROM `admin`
+ WHERE `username` = :user");
+ $stmt->execute(array(':user' => $username));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!verify_ssha256($row['password'], $password_old)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ if (!empty($password_new2) && !empty($password_new)) {
+ if ($password_new2 != $password_new) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_mismatch'])
+ );
+ return false;
+ }
+ if (strlen($password_new) < "6" ||
+ !preg_match('/[A-Za-z]/', $password_new) ||
+ !preg_match('/[0-9]/', $password_new)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_complexity'])
+ );
+ return false;
+ }
+ $password_hashed = hash_password($password_new);
+ try {
+ $stmt = $pdo->prepare("UPDATE `admin` SET `modified` = :modified, `password` = :password_hashed WHERE `username` = :username");
+ $stmt->execute(array(
+ ':password_hashed' => $password_hashed,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':username' => $username
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
+ );
+ }
+}
+function get_admin_details() {
+ // No parameter to be given, only one admin should exist
+ global $pdo;
+ global $lang;
+ $data = array();
+ if ($_SESSION['mailcow_cc_role'] != 'admin') {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin`WHERE `superadmin`='1' AND active='1'");
+ $stmt->execute();
+ $data = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ }
+ return $data;
+}
+function dkim_add_key($postarray) {
+ global $lang;
+ global $pdo;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ // $_SESSION['return'] = array(
+ // 'type' => 'danger',
+ // 'msg' => sprintf($lang['danger']['access_denied'])
+ // );
+ // return false;
+ // }
+ $key_length = intval($postarray['key_size']);
+ $domain = $postarray['domain'];
+ if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ return false;
+ }
+
+ if (!empty(glob($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ return false;
+ }
+
+ $config = array(
+ "digest_alg" => "sha256",
+ "private_key_bits" => $key_length,
+ "private_key_type" => OPENSSL_KEYTYPE_RSA,
+ );
+ if ($keypair_ressource = openssl_pkey_new($config)) {
+ $key_details = openssl_pkey_get_details($keypair_ressource);
+ $pubKey = implode(array_slice(
+ array_filter(
+ explode(PHP_EOL, $key_details['key'])
+ ), 1, -1)
+ );
+ // Save public key to file
+ file_put_contents($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim', $pubKey);
+ // Save private key to file
+ openssl_pkey_export_to_file($keypair_ressource, $GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim');
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['dkim_added'])
+ );
+ return true;
+ }
+ else {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ return false;
+ }
+}
+function dkim_get_key_details($domain) {
+ $data = array();
+ if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $dkim_pubkey_file = escapeshellarg($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
+ if (file_exists(substr($dkim_pubkey_file, 1, -1))) {
+ $data['pubkey'] = file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
+ $data['length'] = (strlen($data['pubkey']) < 391) ? 1024 : 2048;
+ $data['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . file_get_contents($GLOBALS["MC_DKIM_TXTS"]. "/" . $domain . "." . "dkim");
+ }
+ }
+ return $data;
+}
+function dkim_get_blind_keys() {
+ global $lang;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ $domains = array();
+ $dnstxt_folder = scandir($GLOBALS["MC_DKIM_TXTS"]);
+ $dnstxt_files = array_diff($dnstxt_folder, array('.', '..'));
+ foreach($dnstxt_files as $file) {
+ $domains[] = substr($file, 0, -5);
+ }
+ return array_diff($domains, array_merge(mailbox_get_domains(), mailbox_get_alias_domains()));
+}
+function dkim_delete_key($postarray) {
+ global $lang;
+ $domain = $postarray['domain'];
+
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ // if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ // $_SESSION['return'] = array(
+ // 'type' => 'danger',
+ // 'msg' => sprintf($lang['danger']['access_denied'])
+ // );
+ // return false;
+ // }
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
+ );
+ return false;
+ }
+ exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_TXTS'] . '/' . $domain . '.dkim'), $out, $return);
+ if ($return != "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
+ );
+ return false;
+ }
+ exec('rm ' . escapeshellarg($GLOBALS['MC_DKIM_KEYS'] . '/' . $domain . '.dkim'), $out, $return);
+ if ($return != "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['dkim_remove_failed'])
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['dkim_removed'])
+ );
+ return true;
+}
+function mailbox_add_domain($postarray) {
+ // Array elements
+ // domain string
+ // description string
+ // aliases int
+ // mailboxes int
+ // maxquota int
+ // quota int
+ // active int
+ // relay_all_recipients int
+ // backupmx int
+ global $pdo;
+ global $lang;
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ $domain = idn_to_ascii(strtolower(trim($postarray['domain'])));
+ $description = $postarray['description'];
+ $aliases = $postarray['aliases'];
+ $mailboxes = $postarray['mailboxes'];
+ $maxquota = $postarray['maxquota'];
+ $quota = $postarray['quota'];
+
+ if ($maxquota > $quota) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
+ );
+ return false;
+ }
+
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
+ isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
+ isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true;
+
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+
+ foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) {
+ if (!is_numeric($data)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['object_is_not_numeric'], htmlspecialchars($data))
+ );
+ return false;
+ }
+ }
+
+ try {
+ $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));
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if ($num_results != 0) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_exists'], htmlspecialchars($domain))
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `created`, `modified`, `active`, `relay_all_recipients`)
+ VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, 'virtual', :backupmx, :created, :modified, :active, :relay_all_recipients)");
+ $stmt->execute(array(
+ ':domain' => $domain,
+ ':description' => $description,
+ ':aliases' => $aliases,
+ ':mailboxes' => $mailboxes,
+ ':maxquota' => $maxquota,
+ ':quota' => $quota,
+ ':backupmx' => $backupmx,
+ ':active' => $active,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':relay_all_recipients' => $relay_all_recipients
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_added'], htmlspecialchars($domain))
+ );
+ }
+ catch (PDOException $e) {
+ mailbox_delete_domain(array('domain' => $domain));
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_add_alias($postarray) {
+ // Array elements
+ // address string (separated by " ", "," ";" "\n") - email address or domain
+ // goto string (separated by " ", "," ";" "\n")
+ // active int
+ global $lang;
+ global $pdo;
+ $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['address']));
+ $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto']));
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ if (empty($addresses[0])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_empty'])
+ );
+ return false;
+ }
+
+ if (empty($gotos[0])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['goto_empty'])
+ );
+ return false;
+ }
+
+ foreach ($addresses as $address) {
+ if (empty($address)) {
+ continue;
+ }
+
+ $domain = idn_to_ascii(substr(strstr($address, '@'), 1));
+ $local_part = strstr($address, '@', true);
+ $address = $local_part.'@'.$domain;
+
+ try {
+ $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',
+ 'msg' => sprintf($lang['danger']['domain_not_found'], $domain)
+ );
+ return false;
+ }
+
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias`
+ 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',
+ 'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
+ );
+ return false;
+ }
+
+ $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',
+ 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($address))
+ );
+ return false;
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_invalid'])
+ );
+ return false;
+ }
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ foreach ($gotos as &$goto) {
+ if (empty($goto)) {
+ continue;
+ }
+
+ $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1));
+ $goto_local_part = strstr($goto, '@', true);
+ $goto = $goto_local_part.'@'.$goto_domain;
+
+ if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['goto_invalid'])
+ );
+ return false;
+ }
+ if ($goto == $address) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_goto_identical'])
+ );
+ return false;
+ }
+ }
+
+ $gotos = array_filter($gotos);
+ $goto = implode(",", $gotos);
+
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`)
+ VALUES (:address, :goto, :domain, :created, :modified, :active)");
+
+ if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
+ $stmt->execute(array(
+ ':address' => '@'.$domain,
+ ':goto' => $goto,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ else {
+ $stmt->execute(array(
+ ':address' => $address,
+ ':goto' => $goto,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['alias_added'])
+ );
+ }
+ catch (PDOException $e) {
+ mailbox_delete_alias(array('address' => $address));
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['alias_added'])
+ );
+}
+function mailbox_add_alias_domain($postarray) {
+ // Array elements
+ // active int
+ // alias_domain string
+ // target_domain string
+ global $lang;
+ global $pdo;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain'])));
+ $target_domain = idn_to_ascii(strtolower(trim($postarray['target_domain'])));
+
+ if (!is_valid_domain_name($alias_domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
+ );
+ return false;
+ }
+
+ if (!is_valid_domain_name($target_domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['target_domain_invalid'])
+ );
+ return false;
+ }
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ if ($alias_domain == $target_domain) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
+ );
+ return false;
+ }
+
+ try {
+ $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',
+ 'msg' => sprintf($lang['danger']['targetd_not_found'])
+ );
+ return false;
+ }
+
+ $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain
+ UNION
+ SELECT `alias_domain` FROM `alias_domain` WHERE `alias_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',
+ 'msg' => sprintf($lang['danger']['aliasd_exists'])
+ );
+ return false;
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `created`, `modified`, `active`)
+ VALUES (:alias_domain, :target_domain, :created, :modified, :active)");
+ $stmt->execute(array(
+ ':alias_domain' => $alias_domain,
+ ':target_domain' => $target_domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['aliasd_added'], htmlspecialchars($alias_domain))
+ );
+ }
+ catch (PDOException $e) {
+ mailbox_delete_alias_domain(array('alias_domain' => $alias_domain));
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_add_mailbox($postarray) {
+ // Array elements
+ // active int
+ // local_part string
+ // domain string
+ // name string (username if empty)
+ // password string
+ // password2 string
+ // quota int (MiB)
+ // active int
+
+ global $pdo;
+ global $lang;
+ $local_part = strtolower(trim($postarray['local_part']));
+ $domain = idn_to_ascii(strtolower(trim($postarray['domain'])));
+ $username = $local_part . '@' . $domain;
+ if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_invalid'])
+ );
+ return false;
+ }
+ if (empty($postarray['local_part'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_invalid'])
+ );
+ return false;
+ }
+ $password = $postarray['password'];
+ $password2 = $postarray['password2'];
+ $name = $postarray['name'];
+ $quota_m = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT);
+
+ if (empty($name)) {
+ $name = $local_part;
+ }
+
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+
+ $quota_b = ($quota_m * 1048576);
+ $maildir = $domain."/".$local_part."/";
+
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $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 `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',
+ 'msg' => sprintf($lang['danger']['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',
+ 'msg' => sprintf($lang['danger']['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',
+ 'msg' => sprintf($lang['danger']['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',
+ 'msg' => sprintf($lang['danger']['domain_not_found'], $domain)
+ );
+ return false;
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if (!is_numeric($quota_m) || $quota_m == "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'])
+ );
+ return false;
+ }
+
+ if (!empty($password) && !empty($password2)) {
+ if ($password != $password2) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_mismatch'])
+ );
+ return false;
+ }
+ $password_hashed = hash_password($password);
+ }
+ else {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_empty'])
+ );
+ return false;
+ }
+
+ if ($MailboxData['count'] >= $DomainData['mailboxes']) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['max_mailbox_exceeded'], $MailboxData['count'], $DomainData['mailboxes'])
+ );
+ return false;
+ }
+
+ if ($quota_m > $DomainData['maxquota']) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['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',
+ 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m)
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `created`, `modified`, `active`)
+ VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :created, :modified, :active)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':password_hashed' => $password_hashed,
+ ':name' => $name,
+ ':maildir' => $maildir,
+ ':quota_b' => $quota_b,
+ ':local_part' => $local_part,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+
+ $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
+ VALUES (:username, '0', '0')");
+ $stmt->execute(array(':username' => $username));
+
+ $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`)
+ VALUES (:username1, :username2, :domain, :created, :modified, :active)");
+ $stmt->execute(array(
+ ':username1' => $username,
+ ':username2' => $username,
+ ':domain' => $domain,
+ ':created' => date('Y-m-d H:i:s'),
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['mailbox_added'], htmlspecialchars($username))
+ );
+ }
+ catch (PDOException $e) {
+ mailbox_delete_mailbox(array('address' => $username));
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_edit_alias_domain($postarray) {
+ // Array elements
+ // active int
+ // alias_domain_now string
+ // alias_domain string
+ global $lang;
+ global $pdo;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain'])));
+ $alias_domain_now = strtolower(trim($postarray['alias_domain_now']));
+ if (!is_valid_domain_name($alias_domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
+ );
+ return false;
+ }
+
+ if (!is_valid_domain_name($alias_domain_now)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
+ WHERE `alias_domain`= :alias_domain_now");
+ $stmt->execute(array(':alias_domain_now' => $alias_domain_now));
+ $DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
+ WHERE `target_domain`= :alias_domain");
+ $stmt->execute(array(':alias_domain' => $alias_domain));
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if ($num_results != 0) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("UPDATE `alias_domain` SET
+ `alias_domain` = :alias_domain,
+ `active` = :active,
+ `modified` = :modified,
+ WHERE `alias_domain` = :alias_domain_now");
+ $stmt->execute(array(
+ ':alias_domain' => $alias_domain,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':alias_domain_now' => $alias_domain_now,
+ ':active' => $active
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['aliasd_modified'], htmlspecialchars($alias_domain))
+ );
+}
+function mailbox_edit_alias($postarray) {
+ // Array elements
+ // address string
+ // goto string (separated by " ", "," ";" "\n") - email address or domain
+ // active int
+ global $lang;
+ global $pdo;
+ $address = $postarray['address'];
+ $domain = idn_to_ascii(substr(strstr($address, '@'), 1));
+ $local_part = strstr($address, '@', true);
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (empty($postarray['goto'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['goto_empty'])
+ );
+ return false;
+ }
+ $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto']));
+ foreach ($gotos as &$goto) {
+ if (empty($goto)) {
+ continue;
+ }
+ if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' =>sprintf($lang['danger']['goto_invalid'])
+ );
+ return false;
+ }
+ if ($goto == $address) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_goto_identical'])
+ );
+ return false;
+ }
+ }
+ $gotos = array_filter($gotos);
+ $goto = implode(",", $gotos);
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['alias_invalid'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("UPDATE `alias` SET
+ `goto` = :goto,
+ `active`= :active,
+ `modified` = :modified,
+ WHERE `address` = :address");
+ $stmt->execute(array(
+ ':goto' => $goto,
+ ':active' => $active,
+ ':address' => $address,
+ ':modified' => date('Y-m-d H:i:s'),
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['alias_modified'], htmlspecialchars($address))
+ );
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_edit_domain($postarray) {
+ // Array elements
+ // domain string
+ // description string
+ // active int
+ // relay_all_recipients int
+ // backupmx int
+ // aliases float
+ // mailboxes float
+ // maxquota float
+ // quota float (Byte)
+ // active int
+
+ global $lang;
+ global $pdo;
+
+ $domain = idn_to_ascii($postarray['domain']);
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+
+ if ($_SESSION['mailcow_cc_role'] == "domainadmin" && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $description = $postarray['description'];
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ try {
+ $stmt = $pdo->prepare("UPDATE `domain` SET
+ `modified`= :modified,
+ `description` = :description
+ WHERE `domain` = :domain");
+ $stmt->execute(array(
+ ':modified' => date('Y-m-d H:i:s'),
+ ':description' => $description,
+ ':domain' => $domain
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain))
+ );
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ elseif ($_SESSION['mailcow_cc_role'] == "admin") {
+ $description = $postarray['description'];
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ $aliases = filter_var($postarray['aliases'], FILTER_SANITIZE_NUMBER_FLOAT);
+ $mailboxes = filter_var($postarray['mailboxes'], FILTER_SANITIZE_NUMBER_FLOAT);
+ $maxquota = filter_var($postarray['maxquota'], FILTER_SANITIZE_NUMBER_FLOAT);
+ $quota = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT);
+ isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
+ isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
+ isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true;
+ try {
+ // GET MAILBOX DATA
+ $stmt = $pdo->prepare("SELECT
+ COUNT(*) AS count,
+ MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `maxquota`,
+ COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota`
+ FROM `mailbox`
+ WHERE domain= :domain");
+ $stmt->execute(array(':domain' => $domain));
+ $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
+ // GET ALIAS DATA
+ $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);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if ($maxquota > $quota) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
+ );
+ return false;
+ }
+
+ if ($MailboxData['maxquota'] > $maxquota) {
+ echo $MailboxData['maxquota'];
+ die();
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['max_quota_in_use'], $MailboxData['maxquota'])
+ );
+ return false;
+ }
+
+ if ($MailboxData['quota'] > $quota) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_quota_m_in_use'], $MailboxData['quota'])
+ );
+ return false;
+ }
+
+ if ($MailboxData['count'] > $mailboxes) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailboxes_in_use'], $MailboxData['count'])
+ );
+ return false;
+ }
+
+ if ($AliasData['count'] > $aliases) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['aliases_in_use'], $AliasData['count'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("UPDATE `domain` SET
+ `modified`= :modified,
+ `relay_all_recipients` = :relay_all_recipients,
+ `backupmx` = :backupmx,
+ `active` = :active,
+ `quota` = :quota,
+ `maxquota` = :maxquota,
+ `modified` = :modified,
+ `mailboxes` = :mailboxes,
+ `aliases` = :aliases,
+ `description` = :description
+ WHERE `domain` = :domain");
+ $stmt->execute(array(
+ ':relay_all_recipients' => $relay_all_recipients,
+ ':backupmx' => $backupmx,
+ ':active' => $active,
+ ':quota' => $quota,
+ ':maxquota' => $maxquota,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':mailboxes' => $mailboxes,
+ ':aliases' => $aliases,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':description' => $description,
+ ':domain' => $domain
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain))
+ );
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+}
+function mailbox_edit_mailbox($postarray) {
+ global $lang;
+ global $pdo;
+ isset($postarray['active']) ? $active = '1' : $active = '0';
+ if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['username_invalid'])
+ );
+ return false;
+ }
+ $quota_m = $postarray['quota'];
+ $quota_b = $quota_m*1048576;
+ $username = $postarray['username'];
+ $name = $postarray['name'];
+ $password = $postarray['password'];
+ $password2 = $postarray['password2'];
+
+ try {
+ $stmt = $pdo->prepare("SELECT `domain`
+ FROM `mailbox`
+ WHERE username = :username");
+ $stmt->execute(array(':username' => $username));
+ $MailboxData1 = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $stmt = $pdo->prepare("SELECT
+ COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_now`
+ FROM `mailbox`
+ WHERE `username` = :username");
+ $stmt->execute(array(':username' => $username));
+ $MailboxData2 = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $stmt = $pdo->prepare("SELECT
+ COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_in_use`
+ FROM `mailbox`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $MailboxData1['domain']));
+ $MailboxData3 = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $stmt = $pdo->prepare("SELECT `quota`, `maxquota`
+ FROM `domain`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $MailboxData1['domain']));
+ $DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $MailboxData1['domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (!is_numeric($quota_m) || $quota_m == "0") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'], htmlspecialchars($quota_m))
+ );
+ return false;
+ }
+ if ($quota_m > $DomainData['maxquota']) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota'])
+ );
+ return false;
+ }
+ if (($MailboxData3['quota_m_in_use'] - $MailboxData2['quota_m_now'] + $quota_m) > $DomainData['quota']) {
+ $quota_left_m = ($DomainData['quota'] - $MailboxData3['quota_m_in_use'] + $MailboxData2['quota_m_now']);
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m)
+ );
+ return false;
+ }
+
+ // 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
+ (isset($postarray['sender_acl'])) ? $sender_acl_domain_admin = $postarray['sender_acl'] : $sender_acl_domain_admin = array();
+
+ if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) {
+ // Check items in POST array
+ foreach ($sender_acl_domain_admin as $sender_acl) {
+ if (!filter_var($sender_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($sender_acl, '@'))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
+ );
+ return false;
+ }
+ if (is_valid_domain_name(ltrim($sender_acl, '@'))) {
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], ltrim($sender_acl, '@'))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
+ );
+ return false;
+ }
+ }
+ if (filter_var($sender_acl, FILTER_VALIDATE_EMAIL)) {
+ if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $sender_acl)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
+ );
+ return false;
+ }
+ }
+ }
+
+ // Merge both arrays
+ $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin);
+
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
+ $stmt->execute(array(
+ ':username' => $username
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ foreach ($sender_acl_merged as $sender_acl) {
+ $domain = ltrim($sender_acl, '@');
+ if (is_valid_domain_name($domain)) {
+ $sender_acl = '@' . $domain;
+ }
+ try {
+ $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
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ }
+ else {
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
+ $stmt->execute(array(
+ ':username' => $username
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ if (!empty($password) && !empty($password2)) {
+ if ($password != $password2) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['password_mismatch'])
+ );
+ return false;
+ }
+ $password_hashed = hash_password($password);
+ try {
+ $stmt = $pdo->prepare("UPDATE `alias` SET
+ `modified` = :modified,
+ `active` = :active
+ WHERE `address` = :address");
+ $stmt->execute(array(
+ ':address' => $username,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ $stmt = $pdo->prepare("UPDATE `mailbox` SET
+ `modified` = :modified,
+ `active` = :active,
+ `password` = :password_hashed,
+ `name`= :name,
+ `quota` = :quota_b
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':modified' => date('Y-m-d H:i:s'),
+ ':password_hashed' => $password_hashed,
+ ':active' => $active,
+ ':name' => $name,
+ ':quota_b' => $quota_b,
+ ':username' => $username
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
+ );
+ return true;
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ try {
+ $stmt = $pdo->prepare("UPDATE `alias` SET
+ `modified` = :modified,
+ `active` = :active
+ WHERE `address` = :address");
+ $stmt->execute(array(
+ ':address' => $username,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':active' => $active
+ ));
+ $stmt = $pdo->prepare("UPDATE `mailbox` SET
+ `modified` = :modified,
+ `active` = :active,
+ `name`= :name,
+ `quota` = :quota_b
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':active' => $active,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':name' => $name,
+ ':quota_b' => $quota_b,
+ ':username' => $username
+ ));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
+ );
+ return true;
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+}
+function mailbox_get_mailboxes($domain = null) {
+ global $lang;
+ global $pdo;
+ $mailboxes = array();
+ if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ try {
+ $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `domain` != 'ALL' AND `domain` = :domain");
+ $stmt->execute(array(
+ ':domain' => $domain,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $mailboxes[] = $row['username'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ else {
+ try {
+ $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` 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)) {
+ $mailboxes[] = $row['username'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ return $mailboxes;
+}
+function mailbox_get_alias_domains($domain = null) {
+ // Get all domains assigned to mailcow_cc_username or domain, if set
+ // Domain admin needs to be active
+ // Domain does not need to be active
+ global $lang;
+ global $pdo;
+ $aliasdomains = array();
+ if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ try {
+ $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain");
+ $stmt->execute(array(
+ ':domain' => $domain,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $aliasdomains[] = $row['alias_domain'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ else {
+ try {
+ $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'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ }
+ return $aliasdomains;
+}
+function mailbox_get_aliases($domain) {
+ global $lang;
+ global $pdo;
+ $aliases = array();
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain");
+ $stmt->execute(array(
+ ':domain' => $domain,
+ ));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while($row = array_shift($rows)) {
+ $aliases[] = $row['address'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ return $aliases;
+}
+function mailbox_get_alias_details($address) {
+ global $lang;
+ global $pdo;
+ $aliasdata = array();
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `domain`,
+ `goto`,
+ `address`,
+ `active` as `active_int`,
+ CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
+ `created`,
+ `modified`
+ FROM `alias`
+ WHERE `address` = :address AND `address` != `goto`");
+ $stmt->execute(array(
+ ':address' => $address,
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $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['active_int'] = $row['active_int'];
+ $aliasdata['created'] = $row['created'];
+ $aliasdata['modified'] = $row['modified'];
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ return $aliasdata;
+}
+function mailbox_get_alias_domain_details($aliasdomain) {
+ global $lang;
+ global $pdo;
+ $aliasdomaindata = array();
+ try {
+ $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' => $aliasdomain,
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $aliasdomaindata['alias_domain'] = $row['alias_domain'];
+ $aliasdomaindata['target_domain'] = $row['target_domain'];
+ $aliasdomaindata['active'] = $row['active'];
+ $aliasdomaindata['active_int'] = $row['active_int'];
+ $aliasdomaindata['created'] = $row['created'];
+ $aliasdomaindata['modified'] = $row['modified'];
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ return $aliasdomaindata;
+}
+function mailbox_get_domains() {
+ // Get all domains assigned to mailcow_cc_username
+ // Domain admin needs to be active
+ // Domain does not need to be active
+ global $lang;
+ global $pdo;
+
+ try {
+ $domains = array();
+ $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
+ WHERE (`domain` IN (
+ SELECT `domain` from `domain_admins`
+ WHERE (`active`='1' AND `username` = :username))
+ )
+ OR ('admin'= :role)
+ AND `domain` != 'ALL'");
+ $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'];
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ return $domains;
+}
+function mailbox_get_domain_details($domain) {
+ global $lang;
+ global $pdo;
+
+ $domaindata = array();
+ $domain = idn_to_ascii(strtolower(trim($domain)));
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("SELECT
+ `domain`,
+ `description`,
+ `aliases`,
+ `mailboxes`,
+ `maxquota`,
+ `quota`,
+ `relay_all_recipients` as `relay_all_recipients_int`,
+ `backupmx` as `backupmx_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 `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`,
+ CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
+ FROM `domain` WHERE `domain`= :domain");
+ $stmt->execute(array(
+ ':domain' => $domain,
+ ));
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $row['domain']));
+ $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ $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['quota_used_in_domain'] = $MailboxDataDomain['in_use'];
+ $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['max_quota_for_mbox'] = $row['maxquota'] * 1048576;
+ $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576;
+ $domaindata['backupmx'] = $row['backupmx'];
+ $domaindata['backupmx_int'] = $row['backupmx_int'];
+ $domaindata['active'] = $row['active'];
+ $domaindata['active_int'] = $row['active_int'];
+ $domaindata['relay_all_recipients'] = $row['relay_all_recipients'];
+ $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int'];
+
+ $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_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);
+ (isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0";
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ return $domaindata;
+}
+function mailbox_get_mailbox_details($mailbox) {
+ global $lang;
+ global $pdo;
+ $mailboxdata = array();
+ try {
+ $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`.`quota`,
+ `quota2`.`bytes`,
+ `quota2`.`messages`
+ FROM `mailbox`, `quota2`, `domain`
+ WHERE `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");
+ $stmt->execute(array(
+ ':mailbox' => $mailbox,
+ ));
+ $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 COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `domain` = :domain AND `username` != :username");
+ $stmt->execute(array(':domain' => $row['domain'], ':username' => $row['username']));
+ $MailboxUsage = $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'];
+ $mailboxdata['is_relayed'] = $row['backupmx'];
+ $mailboxdata['name'] = $row['name'];
+ $mailboxdata['active'] = $row['active'];
+ $mailboxdata['active_int'] = $row['active_int'];
+ $mailboxdata['domain'] = $row['domain'];
+ $mailboxdata['quota'] = $row['quota'];
+ $mailboxdata['quota_used'] = intval($row['bytes']);
+ $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100);
+ $mailboxdata['messages'] = $row['messages'];
+ if ($mailboxdata['percent_in_use'] >= 90) {
+ $mailboxdata['percent_class'] = "danger";
+ }
+ elseif ($mailboxdata['percent_in_use'] >= 75) {
+ $mailboxdata['percent_class'] = "warning";
+ }
+ else {
+ $mailboxdata['percent_class'] = "success";
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if (!isset($mailboxdata['domain']) ||
+ (isset($mailboxdata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailboxdata['domain']))) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ return $mailboxdata;
+}
+function mailbox_delete_domain($postarray) {
+ global $lang;
+ global $pdo;
+ $domain = $postarray['domain'];
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (!is_valid_domain_name($domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ $domain = strtolower(trim($domain));
+
+
+ try {
+ $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
+ WHERE `domain` = :domain");
+ $stmt->execute(array(':domain' => $domain));
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ if ($num_results != 0 || !empty($num_results)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_not_empty'])
+ );
+ return false;
+ }
+
+ try {
+ $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` = :domain");
+ $stmt->execute(array(
+ ':domain' => '%@'.$domain,
+ ));
+ $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` = :domain");
+ $stmt->execute(array(
+ ':domain' => '%@'.$domain,
+ ));
+ $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain");
+ $stmt->execute(array(
+ ':domain' => '%@'.$domain,
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['domain_removed'], htmlspecialchars($domain))
+ );
+ return true;
+}
+function mailbox_delete_alias($postarray) {
+ global $lang;
+ global $pdo;
+ $address = $postarray['address'];
+ $local_part = strstr($address, '@', true);
+ $domain = mailbox_get_alias_details($address)['domain'];
+ try {
+ $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address");
+ $stmt->execute(array(':address' => $address));
+ $gotos = $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $goto_array = explode(',', $gotos['goto']);
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `address` = :address AND `address` NOT IN (SELECT `username` FROM `mailbox`)");
+ $stmt->execute(array(
+ ':address' => $address
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['alias_removed'], htmlspecialchars($address))
+ );
+
+}
+function mailbox_delete_alias_domain($postarray) {
+ global $lang;
+ global $pdo;
+ if (!is_valid_domain_name($postarray['alias_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['domain_invalid'])
+ );
+ return false;
+ }
+ $alias_domain = $postarray['alias_domain'];
+ try {
+ $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);
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $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,
+ ));
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['alias_domain_removed'], htmlspecialchars($alias_domain))
+ );
+}
+function mailbox_delete_mailbox($postarray) {
+ global $lang;
+ global $pdo;
+ $username = $postarray['username'];
+ $domain = mailbox_get_mailbox_details($username)['domain'];
+ if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+ if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ return false;
+ }
+
+ try {
+ $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :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 `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
+ ));
+ $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("SELECT `address`, `goto` FROM `alias`
+ WHERE `goto` LIKE :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,
+ `modified` = :modified,
+ WHERE `address` = :address");
+ $stmt->execute(array(
+ ':goto' => $gotos_rebuild,
+ ':modified' => date('Y-m-d H:i:s'),
+ ':address' => $gotos['address']
+ ));
+ }
+ }
+ catch (PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['mailbox_removed'], htmlspecialchars($username))
+ );
+}
+function mailbox_get_sender_acl_handles($mailbox) {
+ global $pdo;
+ global $lang;
+ if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => sprintf($lang['danger']['access_denied'])
+ );
+ 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();
+
+ try {
+ $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :goto AND `address` NOT LIKE '@%'");
+ $stmt->execute(array(':goto' => $mailbox));
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while ($row = array_shift($rows)) {
+ $data['fixed_sender_aliases'][] = $row['address'];
+ }
+
+ // 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 `send_as` LIKE '@%'");
+ $stmt->execute(array(':logged_in_as' => $mailbox));
+ $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;
+ }
+ }
+
+ $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` NOT LIKE '@%'");
+ $stmt->execute(array(':logged_in_as' => $mailbox));
+ $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while ($address_row = array_shift($address_rows)) {
+ if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasMailboxObjectAccess($_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) && hasMailboxObjectAccess($_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_as
+ AND `send_as` LIKE '@%')");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ));
+ $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'];
+ $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
+ WHERE `target_domain` = :target_domain
+ AND `alias_domain` NOT IN (
+ SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl`
+ WHERE `logged_in_as` = :logged_in_as
+ AND `send_as` LIKE '@%')");
+ $stmt->execute(array(
+ ':target_domain' => $row_domain['domain'],
+ ':logged_in_as' => $mailbox,
+ ));
+ $rows_ad = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ while ($row_ad = array_shift($rows_ad)) {
+ if (is_valid_domain_name($row_ad['alias_domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_ad['alias_domain'])) {
+ $data['sender_acl_domains']['selectable'][] = $row_ad['alias_domain'];
+ }
+ }
+ }
+ }
+
+ $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 `send_as` NOT LIKE '@%')");
+ $stmt->execute(array(
+ ':logged_in_as' => $mailbox,
+ ':goto' => $mailbox
+ ));
+ while ($row = array_shift($rows)) {
+ if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
+ $data['sender_acl_addresses']['selectable'][] = $row['address'];
+ }
+ }
+ }
+ catch(PDOException $e) {
+ $_SESSION['return'] = array(
+ 'type' => 'danger',
+ 'msg' => 'MySQL: '.$e
+ );
+ return false;
+ }
+ return $data;
+}
+function get_u2f_registrations($username) {
+ global $pdo;
+ $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `username` = ?");
+ $sel->execute(array($username));
+ return $sel->fetchAll(PDO::FETCH_OBJ);
+}
+function add_u2f_registration($username, $reg) {
+ global $pdo;
+ global $lang;
+ $ins = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) values (?, 'u2f', ?, ?, ?, ?)");
+ $ins->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
+ $_SESSION['return'] = array(
+ 'type' => 'success',
+ 'msg' => sprintf($lang['success']['object_modified'], $username)
+ );
+}
+function edit_u2f_registration($reg) {
+ global $pdo;
+ $upd = $pdo->prepare("update tfa set counter = ? where id = ?");
+ $upd->execute(array($reg->counter, $reg->id));
+}
?>
diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php
index e2d0c865..7dcb8638 100644
--- a/data/web/inc/header.inc.php
+++ b/data/web/inc/header.inc.php
@@ -68,11 +68,11 @@
<li <?=(preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? 'class="active"' : ''?>><a href="/mailbox.php"><?=$lang['header']['mailboxes'];?></a></li>
<?php
}
- if ($_SESSION['mailcow_cc_role'] == "user") {
+ if ($_SESSION['mailcow_cc_role'] != "admin") {
?>
<li <?=(preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? 'class="active"' : ''?>><a href="/user.php"><?=$lang['header']['user_settings'];?></a></li>
<?php
- }
+ }
}
?>
</ul>
diff --git a/data/web/inc/init.sql b/data/web/inc/init.sql
index 624f3398..1bf9172c 100644
--- a/data/web/inc/init.sql
+++ b/data/web/inc/init.sql
@@ -127,6 +127,19 @@ CREATE TABLE IF NOT EXISTS `imapsync` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+CREATE TABLE IF NOT EXISTS `tfa` (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `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',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
DROP VIEW IF EXISTS grouped_mail_aliases;
DROP VIEW IF EXISTS grouped_sender_acl;
DROP VIEW IF EXISTS grouped_domain_alias_address;
@@ -148,7 +161,7 @@ SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '
LEFT OUTER JOIN alias_domain on target_domain=domain GROUP BY username;
CREATE TABLE IF NOT EXISTS sogo_acl (
- c_folder_id INTeger NOT NULL,
+ c_folder_id INTEGER NOT NULL,
c_object character varying(255) NOT NULL,
c_uid character varying(255) NOT NULL,
c_role character varying(80) NOT NULL
diff --git a/data/web/inc/lib/U2F.php b/data/web/inc/lib/U2F.php
new file mode 100644
index 00000000..b79d7fac
--- /dev/null
+++ b/data/web/inc/lib/U2F.php
@@ -0,0 +1,506 @@
+<?php
+/* Copyright (c) 2014 Yubico AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+namespace u2flib_server;
+
+/** Constant for the version of the u2f protocol */
+const U2F_VERSION = "U2F_V2";
+
+/** Error for the authentication message not matching any outstanding
+ * authentication request */
+const ERR_NO_MATCHING_REQUEST = 1;
+
+/** Error for the authentication message not matching any registration */
+const ERR_NO_MATCHING_REGISTRATION = 2;
+
+/** Error for the signature on the authentication message not verifying with
+ * the correct key */
+const ERR_AUTHENTICATION_FAILURE = 3;
+
+/** Error for the challenge in the registration message not matching the
+ * registration challenge */
+const ERR_UNMATCHED_CHALLENGE = 4;
+
+/** Error for the attestation signature on the registration message not
+ * verifying */
+const ERR_ATTESTATION_SIGNATURE = 5;
+
+/** Error for the attestation verification not verifying */
+const ERR_ATTESTATION_VERIFICATION = 6;
+
+/** Error for not getting good random from the system */
+const ERR_BAD_RANDOM = 7;
+
+/** Error when the counter is lower than expected */
+const ERR_COUNTER_TOO_LOW = 8;
+
+/** Error decoding public key */
+const ERR_PUBKEY_DECODE = 9;
+
+/** Error user-agent returned error */
+const ERR_BAD_UA_RETURNING = 10;
+
+/** Error old OpenSSL version */
+const ERR_OLD_OPENSSL = 11;
+
+/** @internal */
+const PUBKEY_LEN = 65;
+
+class U2F
+{
+ /** @var string */
+ private $appId;
+
+ /** @var null|string */
+ private $attestDir;
+
+ /** @internal */
+ private $FIXCERTS = array(
+ '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
+ 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
+ '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
+ 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
+ '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
+ 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
+ );
+
+ /**
+ * @param string $appId Application id for the running application
+ * @param string|null $attestDir Directory where trusted attestation roots may be found
+ * @throws Error If OpenSSL older than 1.0.0 is used
+ */
+ public function __construct($appId, $attestDir = null)
+ {
+ if(OPENSSL_VERSION_NUMBER < 0x10000000) {
+ throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
+ }
+ $this->appId = $appId;
+ $this->attestDir = $attestDir;
+ }
+
+ /**
+ * Called to get a registration request to send to a user.
+ * Returns an array of one registration request and a array of sign requests.
+ *
+ * @param array $registrations List of current registrations for this
+ * user, to prevent the user from registering the same authenticator several
+ * times.
+ * @return array An array of two elements, the first containing a
+ * RegisterRequest the second being an array of SignRequest
+ * @throws Error
+ */
+ public function getRegisterData(array $registrations = array())
+ {
+ $challenge = $this->createChallenge();
+ $request = new RegisterRequest($challenge, $this->appId);
+ $signs = $this->getAuthenticateData($registrations);
+ return array($request, $signs);
+ }
+
+ /**
+ * Called to verify and unpack a registration message.
+ *
+ * @param RegisterRequest $request this is a reply to
+ * @param object $response response from a user
+ * @param bool $includeCert set to true if the attestation certificate should be
+ * included in the returned Registration object
+ * @return Registration
+ * @throws Error
+ */
+ public function doRegister($request, $response, $includeCert = true)
+ {
+ if( !is_object( $request ) ) {
+ throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
+ }
+
+ if( !is_object( $response ) ) {
+ throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
+ }
+
+ if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
+ throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
+ }
+
+ if( !is_bool( $includeCert ) ) {
+ throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
+ }
+
+ $rawReg = $this->base64u_decode($response->registrationData);
+ $regData = array_values(unpack('C*', $rawReg));
+ $clientData = $this->base64u_decode($response->clientData);
+ $cli = json_decode($clientData);
+
+ if($cli->challenge !== $request->challenge) {
+ throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
+ }
+
+ $registration = new Registration();
+ $offs = 1;
+ $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
+ $offs += PUBKEY_LEN;
+ // decode the pubKey to make sure it's good
+ $tmpKey = $this->pubkey_to_pem($pubKey);
+ if($tmpKey === null) {
+ throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+ }
+ $registration->publicKey = base64_encode($pubKey);
+ $khLen = $regData[$offs++];
+ $kh = substr($rawReg, $offs, $khLen);
+ $offs += $khLen;
+ $registration->keyHandle = $this->base64u_encode($kh);
+
+ // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
+ $certLen = 4;
+ $certLen += ($regData[$offs + 2] << 8);
+ $certLen += $regData[$offs + 3];
+
+ $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
+ $offs += $certLen;
+ $pemCert = "-----BEGIN CERTIFICATE-----\r\n";
+ $pemCert .= chunk_split(base64_encode($rawCert), 64);
+ $pemCert .= "-----END CERTIFICATE-----";
+ if($includeCert) {
+ $registration->certificate = base64_encode($rawCert);
+ }
+ if($this->attestDir) {
+ if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
+ throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
+ }
+ }
+
+ if(!openssl_pkey_get_public($pemCert)) {
+ throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+ }
+ $signature = substr($rawReg, $offs);
+
+ $dataToVerify = chr(0);
+ $dataToVerify .= hash('sha256', $request->appId, true);
+ $dataToVerify .= hash('sha256', $clientData, true);
+ $dataToVerify .= $kh;
+ $dataToVerify .= $pubKey;
+
+ if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
+ return $registration;
+ } else {
+ throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
+ }
+ }
+
+ /**
+ * Called to get an authentication request.
+ *
+ * @param array $registrations An array of the registrations to create authentication requests for.
+ * @return array An array of SignRequest
+ * @throws Error
+ */
+ public function getAuthenticateData(array $registrations)
+ {
+ $sigs = array();
+ foreach ($registrations as $reg) {
+ if( !is_object( $reg ) ) {
+ throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
+ }
+
+ $sig = new SignRequest();
+ $sig->appId = $this->appId;
+ $sig->keyHandle = $reg->keyHandle;
+ $sig->challenge = $this->createChallenge();
+ $sigs[] = $sig;
+ }
+ return $sigs;
+ }
+
+ /**
+ * Called to verify an authentication response
+ *
+ * @param array $requests An array of outstanding authentication requests
+ * @param array $registrations An array of current registrations
+ * @param object $response A response from the authenticator
+ * @return Registration
+ * @throws Error
+ *
+ * The Registration object returned on success contains an updated counter
+ * that should be saved for future authentications.
+ * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
+ * token cloning or similar and appropriate action should be taken.
+ */
+ public function doAuthenticate(array $requests, array $registrations, $response)
+ {
+ if( !is_object( $response ) ) {
+ throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
+ }
+
+ if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
+ throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
+ }
+
+ /** @var object|null $req */
+ $req = null;
+
+ /** @var object|null $reg */
+ $reg = null;
+
+ $clientData = $this->base64u_decode($response->clientData);
+ $decodedClient = json_decode($clientData);
+ foreach ($requests as $req) {
+ if( !is_object( $req ) ) {
+ throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
+ }
+
+ if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
+ break;
+ }
+
+ $req = null;
+ }
+ if($req === null) {
+ throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
+ }
+ foreach ($registrations as $reg) {
+ if( !is_object( $reg ) ) {
+ throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
+ }
+
+ if($reg->keyHandle === $response->keyHandle) {
+ break;
+ }
+ $reg = null;
+ }
+ if($reg === null) {
+ throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
+ }
+ $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
+ if($pemKey === null) {
+ throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
+ }
+
+ $signData = $this->base64u_decode($response->signatureData);
+ $dataToVerify = hash('sha256', $req->appId, true);
+ $dataToVerify .= substr($signData, 0, 5);
+ $dataToVerify .= hash('sha256', $clientData, true);
+ $signature = substr($signData, 5);
+
+ if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
+ $ctr = unpack("Nctr", substr($signData, 1, 4));
+ $counter = $ctr['ctr'];
+ /* TODO: wrap-around should be handled somehow.. */
+ if($counter > $reg->counter) {
+ $reg->counter = $counter;
+ return $reg;
+ } else {
+ throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
+ }
+ } else {
+ throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
+ }
+ }
+
+ /**
+ * @return array
+ */
+ private function get_certs()
+ {
+ $files = array();
+ $dir = $this->attestDir;
+ if($dir && $handle = opendir($dir)) {
+ while(false !== ($entry = readdir($handle))) {
+ if(is_file("$dir/$entry")) {
+ $files[] = "$dir/$entry";
+ }
+ }
+ closedir($handle);
+ }
+ return $files;
+ }
+
+ /**
+ * @param string $data
+ * @return string
+ */
+ private function base64u_encode($data)
+ {
+ return trim(strtr(base64_encode($data), '+/', '-_'), '=');
+ }
+
+ /**
+ * @param string $data
+ * @return string
+ */
+ private function base64u_decode($data)
+ {
+ return base64_decode(strtr($data, '-_', '+/'));
+ }
+
+ /**
+ * @param string $key
+ * @return null|string
+ */
+ private function pubkey_to_pem($key)
+ {
+ if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
+ return null;
+ }
+
+ /*
+ * Convert the public key to binary DER format first
+ * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
+ *
+ * SEQUENCE(2 elem) 30 59
+ * SEQUENCE(2 elem) 30 13
+ * OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
+ * OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07
+ * BIT STRING(520 bit) 03 42 ..key..
+ */
+ $der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
+ $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
+ $der .= "\0".$key;
+
+ $pem = "-----BEGIN PUBLIC KEY-----\r\n";
+ $pem .= chunk_split(base64_encode($der), 64);
+ $pem .= "-----END PUBLIC KEY-----";
+
+ return $pem;
+ }
+
+ /**
+ * @return string
+ * @throws Error
+ */
+ private function createChallenge()
+ {
+ $challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
+ if( $crypto_strong !== true ) {
+ throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
+ }
+
+ $challenge = $this->base64u_encode( $challenge );
+
+ return $challenge;
+ }
+
+ /**
+ * Fixes a certificate where the signature contains unused bits.
+ *
+ * @param string $cert
+ * @return mixed
+ */
+ private function fixSignatureUnusedBits($cert)
+ {
+ if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
+ $cert[strlen($cert) - 257] = "\0";
+ }
+ return $cert;
+ }
+}
+
+/**
+ * Class for building a registration request
+ *
+ * @package u2flib_server
+ */
+class RegisterRequest
+{
+ /** Protocol version */
+ public $version = U2F_VERSION;
+
+ /** Registration challenge */
+ public $challenge;
+
+ /** Application id */
+ public $appId;
+
+ /**
+ * @param string $challenge
+ * @param string $appId
+ * @internal
+ */
+ public function __construct($challenge, $appId)
+ {
+ $this->challenge = $challenge;
+ $this->appId = $appId;
+ }
+}
+
+/**
+ * Class for building up an authentication request
+ *
+ * @package u2flib_server
+ */
+class SignRequest
+{
+ /** Protocol version */
+ public $version = U2F_VERSION;
+
+ /** Authentication challenge */
+ public $challenge;
+
+ /** Key handle of a registered authenticator */
+ public $keyHandle;
+
+ /** Application id */
+ public $appId;
+}
+
+/**
+ * Class returned for successful registrations
+ *
+ * @package u2flib_server
+ */
+class Registration
+{
+ /** The key handle of the registered authenticator */
+ public $keyHandle;
+
+ /** The public key of the registered authenticator */
+ public $publicKey;
+
+ /** The attestation certificate of the registered authenticator */
+ public $certificate;
+
+ /** The counter associated with this registration */
+ public $counter = -1;
+}
+
+/**
+ * Error class, returned on errors
+ *
+ * @package u2flib_server
+ */
+class Error extends \Exception
+{
+ /**
+ * Override constructor and make message and code mandatory
+ * @param string $message
+ * @param int $code
+ * @param \Exception|null $previous
+ */
+ public function __construct($message, $code, \Exception $previous = null) {
+ parent::__construct($message, $code, $previous);
+ }
+}
diff --git a/data/web/inc/lib/Yubico.php b/data/web/inc/lib/Yubico.php
new file mode 100644
index 00000000..68a3087b
--- /dev/null
+++ b/data/web/inc/lib/Yubico.php
@@ -0,0 +1,475 @@
+<?php
+ /**
+ * Class for verifying Yubico One-Time-Passcodes
+ *
+ * @category Auth
+ * @package Auth_Yubico
+ * @author Simon Josefsson <[email protected]>, Olov Danielson <[email protected]>
+ * @copyright 2007-2015 Yubico AB
+ * @license http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version 2.0
+ * @link http://www.yubico.com/
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * Class for verifying Yubico One-Time-Passcodes
+ *
+ * Simple example:
+ * <code>
+ * require_once 'Auth/Yubico.php';
+ * $otp = "ccbbddeertkrctjkkcglfndnlihhnvekchkcctif";
+ *
+ * # Generate a new id+key from https://api.yubico.com/get-api-key/
+ * $yubi = new Auth_Yubico('42', 'FOOBAR=');
+ * $auth = $yubi->verify($otp);
+ * if (PEAR::isError($auth)) {
+ * print "<p>Authentication failed: " . $auth->getMessage();
+ * print "<p>Debug output from server: " . $yubi->getLastResponse();
+ * } else {
+ * print "<p>You are authenticated!";
+ * }
+ * </code>
+ */
+class Auth_Yubico
+{
+ /**#@+
+ * @access private
+ */
+
+ /**
+ * Yubico client ID
+ * @var string
+ */
+ var $_id;
+
+ /**
+ * Yubico client key
+ * @var string
+ */
+ var $_key;
+
+ /**
+ * URL part of validation server
+ * @var string
+ */
+ var $_url;
+
+ /**
+ * List with URL part of validation servers
+ * @var array
+ */
+ var $_url_list;
+
+ /**
+ * index to _url_list
+ * @var int
+ */
+ var $_url_index;
+
+ /**
+ * Last query to server
+ * @var string
+ */
+ var $_lastquery;
+
+ /**
+ * Response from server
+ * @var string
+ */
+ var $_response;
+
+ /**
+ * Flag whether to use https or not.
+ * @var boolean
+ */
+ var $_https;
+
+ /**
+ * Flag whether to verify HTTPS server certificates or not.
+ * @var boolean
+ */
+ var $_httpsverify;
+
+ /**
+ * Constructor
+ *
+ * Sets up the object
+ * @param string $id The client identity
+ * @param string $key The client MAC key (optional)
+ * @param boolean $https Flag whether to use https (optional)
+ * @param boolean $httpsverify Flag whether to use verify HTTPS
+ * server certificates (optional,
+ * default true)
+ * @access public
+ */
+ function __construct($id, $key = '', $https = 0, $httpsverify = 1)
+ {
+ $this->_id = $id;
+ $this->_key = base64_decode($key);
+ $this->_https = $https;
+ $this->_httpsverify = $httpsverify;
+ }
+
+ function Auth_Yubico($id, $key = '', $https = 0, $httpsverify = 1)
+ {
+ self::__construct();
+ }
+
+ /**
+ * Specify to use a different URL part for verification.
+ * The default is "api.yubico.com/wsapi/verify".
+ *
+ * @param string $url New server URL part to use
+ * @access public
+ */
+ function setURLpart($url)
+ {
+ $this->_url = $url;
+ }
+
+ /**
+ * Get URL part to use for validation.
+ *
+ * @return string Server URL part
+ * @access public
+ */
+ function getURLpart()
+ {
+ if ($this->_url) {
+ return $this->_url;
+ } else {
+ return "api.yubico.com/wsapi/verify";
+ }
+ }
+
+
+ /**
+ * Get next URL part from list to use for validation.
+ *
+ * @return mixed string with URL part of false if no more URLs in list
+ * @access public
+ */
+ function getNextURLpart()
+ {
+ if ($this->_url_list) $url_list=$this->_url_list;
+ else $url_list=array('api.yubico.com/wsapi/2.0/verify',
+ 'api2.yubico.com/wsapi/2.0/verify',
+ 'api3.yubico.com/wsapi/2.0/verify',
+ 'api4.yubico.com/wsapi/2.0/verify',
+ 'api5.yubico.com/wsapi/2.0/verify');
+
+ if ($this->_url_index>=count($url_list)) return false;
+ else return $url_list[$this->_url_index++];
+ }
+
+ /**
+ * Resets index to URL list
+ *
+ * @access public
+ */
+ function URLreset()
+ {
+ $this->_url_index=0;
+ }
+
+ /**
+ * Add another URLpart.
+ *
+ * @access public
+ */
+ function addURLpart($URLpart)
+ {
+ $this->_url_list[]=$URLpart;
+ }
+
+ /**
+ * Return the last query sent to the server, if any.
+ *
+ * @return string Request to server
+ * @access public
+ */
+ function getLastQuery()
+ {
+ return $this->_lastquery;
+ }
+
+ /**
+ * Return the last data received from the server, if any.
+ *
+ * @return string Output from server
+ * @access public
+ */
+ function getLastResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Parse input string into password, yubikey prefix,
+ * ciphertext, and OTP.
+ *
+ * @param string Input string to parse
+ * @param string Optional delimiter re-class, default is '[:]'
+ * @return array Keyed array with fields
+ * @access public
+ */
+ function parsePasswordOTP($str, $delim = '[:]')
+ {
+ if (!preg_match("/^((.*)" . $delim . ")?" .
+ "(([cbdefghijklnrtuv]{0,16})" .
+ "([cbdefghijklnrtuv]{32}))$/i",
+ $str, $matches)) {
+ /* Dvorak? */
+ if (!preg_match("/^((.*)" . $delim . ")?" .
+ "(([jxe\.uidchtnbpygk]{0,16})" .
+ "([jxe\.uidchtnbpygk]{32}))$/i",
+ $str, $matches)) {
+ return false;
+ } else {
+ $ret['otp'] = strtr($matches[3], "jxe.uidchtnbpygk", "cbdefghijklnrtuv");
+ }
+ } else {
+ $ret['otp'] = $matches[3];
+ }
+ $ret['password'] = $matches[2];
+ $ret['prefix'] = $matches[4];
+ $ret['ciphertext'] = $matches[5];
+ return $ret;
+ }
+
+ /* TODO? Add functions to get parsed parts of server response? */
+
+ /**
+ * Parse parameters from last response
+ *
+ * example: getParameters("timestamp", "sessioncounter", "sessionuse");
+ *
+ * @param array @parameters Array with strings representing
+ * parameters to parse
+ * @return array parameter array from last response
+ * @access public
+ */
+ function getParameters($parameters)
+ {
+ if ($parameters == null) {
+ $parameters = array('timestamp', 'sessioncounter', 'sessionuse');
+ }
+ $param_array = array();
+ foreach ($parameters as $param) {
+ if(!preg_match("/" . $param . "=([0-9]+)/", $this->_response, $out)) {
+ return PEAR::raiseError('Could not parse parameter ' . $param . ' from response');
+ }
+ $param_array[$param]=$out[1];
+ }
+ return $param_array;
+ }
+
+ /**
+ * Verify Yubico OTP against multiple URLs
+ * Protocol specification 2.0 is used to construct validation requests
+ *
+ * @param string $token Yubico OTP
+ * @param int $use_timestamp 1=>send request with ×tamp=1 to
+ * get timestamp and session information
+ * in the response
+ * @param boolean $wait_for_all If true, wait until all
+ * servers responds (for debugging)
+ * @param string $sl Sync level in percentage between 0
+ * and 100 or "fast" or "secure".
+ * @param int $timeout Max number of seconds to wait
+ * for responses
+ * @return mixed PEAR error on error, true otherwise
+ * @access public
+ */
+ function verify($token, $use_timestamp=null, $wait_for_all=False,
+ $sl=null, $timeout=null)
+ {
+ /* Construct parameters string */
+ $ret = $this->parsePasswordOTP($token);
+ if (!$ret) {
+ return PEAR::raiseError('Could not parse Yubikey OTP');
+ }
+ $params = array('id'=>$this->_id,
+ 'otp'=>$ret['otp'],
+ 'nonce'=>md5(uniqid(rand())));
+ /* Take care of protocol version 2 parameters */
+ if ($use_timestamp) $params['timestamp'] = 1;
+ if ($sl) $params['sl'] = $sl;
+ if ($timeout) $params['timeout'] = $timeout;
+ ksort($params);
+ $parameters = '';
+ foreach($params as $p=>$v) $parameters .= "&" . $p . "=" . $v;
+ $parameters = ltrim($parameters, "&");
+
+ /* Generate signature. */
+ if($this->_key <> "") {
+ $signature = base64_encode(hash_hmac('sha1', $parameters,
+ $this->_key, true));
+ $signature = preg_replace('/\+/', '%2B', $signature);
+ $parameters .= '&h=' . $signature;
+ }
+
+ /* Generate and prepare request. */
+ $this->_lastquery=null;
+ $this->URLreset();
+ $mh = curl_multi_init();
+ $ch = array();
+ while($URLpart=$this->getNextURLpart())
+ {
+ /* Support https. */
+ if ($this->_https) {
+ $query = "https://";
+ } else {
+ $query = "http://";
+ }
+ $query .= $URLpart . "?" . $parameters;
+
+ if ($this->_lastquery) { $this->_lastquery .= " "; }
+ $this->_lastquery .= $query;
+
+ $handle = curl_init($query);
+ curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
+ curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
+ if (!$this->_httpsverify) {
+ curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+ curl_setopt($handle, CURLOPT_FAILONERROR, true);
+ /* If timeout is set, we better apply it here as well
+ in case the validation server fails to follow it.
+ */
+ if ($timeout) curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
+ curl_multi_add_handle($mh, $handle);
+
+ $ch[(int)$handle] = $handle;
+ }
+
+ /* Execute and read request. */
+ $this->_response=null;
+ $replay=False;
+ $valid=False;
+ do {
+ /* Let curl do its work. */
+ while (($mrc = curl_multi_exec($mh, $active))
+ == CURLM_CALL_MULTI_PERFORM)
+ ;
+
+ while ($info = curl_multi_info_read($mh)) {
+ if ($info['result'] == CURLE_OK) {
+
+ /* We have a complete response from one server. */
+
+ $str = curl_multi_getcontent($info['handle']);
+ $cinfo = curl_getinfo ($info['handle']);
+
+ if ($wait_for_all) { # Better debug info
+ $this->_response .= 'URL=' . $cinfo['url'] ."\n"
+ . $str . "\n";
+ }
+
+ if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
+ $status = $out[1];
+
+ /*
+ * There are 3 cases.
+ *
+ * 1. OTP or Nonce values doesn't match - ignore
+ * response.
+ *
+ * 2. We have a HMAC key. If signature is invalid -
+ * ignore response. Return if status=OK or
+ * status=REPLAYED_OTP.
+ *
+ * 3. Return if status=OK or status=REPLAYED_OTP.
+ */
+ if (!preg_match("/otp=".$params['otp']."/", $str) ||
+ !preg_match("/nonce=".$params['nonce']."/", $str)) {
+ /* Case 1. Ignore response. */
+ }
+ elseif ($this->_key <> "") {
+ /* Case 2. Verify signature first */
+ $rows = explode("\r\n", trim($str));
+ $response=array();
+ while (list($key, $val) = each($rows)) {
+ /* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */
+ $val = preg_replace('/=/', '#', $val, 1);
+ $row = explode("#", $val);
+ $response[$row[0]] = $row[1];
+ }
+
+ $parameters=array('nonce','otp', 'sessioncounter', 'sessionuse', 'sl', 'status', 't', 'timeout', 'timestamp');
+ sort($parameters);
+ $check=Null;
+ foreach ($parameters as $param) {
+ if (array_key_exists($param, $response)) {
+ if ($check) $check = $check . '&';
+ $check = $check . $param . '=' . $response[$param];
+ }
+ }
+
+ $checksignature =
+ base64_encode(hash_hmac('sha1', utf8_encode($check),
+ $this->_key, true));
+
+ if($response['h'] == $checksignature) {
+ if ($status == 'REPLAYED_OTP') {
+ if (!$wait_for_all) { $this->_response = $str; }
+ $replay=True;
+ }
+ if ($status == 'OK') {
+ if (!$wait_for_all) { $this->_response = $str; }
+ $valid=True;
+ }
+ }
+ } else {
+ /* Case 3. We check the status directly */
+ if ($status == 'REPLAYED_OTP') {
+ if (!$wait_for_all) { $this->_response = $str; }
+ $replay=True;
+ }
+ if ($status == 'OK') {
+ if (!$wait_for_all) { $this->_response = $str; }
+ $valid=True;
+ }
+ }
+ }
+ if (!$wait_for_all && ($valid || $replay))
+ {
+ /* We have status=OK or status=REPLAYED_OTP, return. */
+ foreach ($ch as $h) {
+ curl_multi_remove_handle($mh, $h);
+ curl_close($h);
+ }
+ curl_multi_close($mh);
+ if ($replay) return PEAR::raiseError('REPLAYED_OTP');
+ if ($valid) return true;
+ return PEAR::raiseError($status);
+ }
+
+ curl_multi_remove_handle($mh, $info['handle']);
+ curl_close($info['handle']);
+ unset ($ch[(int)$info['handle']]);
+ }
+ curl_multi_select($mh);
+ }
+ } while ($active);
+
+ /* Typically this is only reached for wait_for_all=true or
+ * when the timeout is reached and there is no
+ * OK/REPLAYED_REQUEST answer (think firewall).
+ */
+
+ foreach ($ch as $h) {
+ curl_multi_remove_handle ($mh, $h);
+ curl_close ($h);
+ }
+ curl_multi_close ($mh);
+
+ if ($replay) return PEAR::raiseError('REPLAYED_OTP');
+ if ($valid) return true;
+ return PEAR::raiseError('NO_VALID_ANSWER');
+ }
+}
+?>
diff --git a/data/web/inc/mailbox.inc.php b/data/web/inc/mailbox.inc.php
deleted file mode 100644
index f8c9f6ea..00000000
--- a/data/web/inc/mailbox.inc.php
+++ /dev/null
@@ -1,1990 +0,0 @@
-<?php
-function mailbox_add_domain($postarray) {
- // Array elements
- // domain string
- // description string
- // aliases int
- // mailboxes int
- // maxquota int
- // quota int
- // active int
- // relay_all_recipients int
- // backupmx int
- global $pdo;
- global $lang;
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- $domain = idn_to_ascii(strtolower(trim($postarray['domain'])));
- $description = $postarray['description'];
- $aliases = $postarray['aliases'];
- $mailboxes = $postarray['mailboxes'];
- $maxquota = $postarray['maxquota'];
- $quota = $postarray['quota'];
-
- if ($maxquota > $quota) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
- );
- return false;
- }
-
- isset($postarray['active']) ? $active = '1' : $active = '0';
- isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
- isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
- isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true;
-
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
-
- foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) {
- if (!is_numeric($data)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['object_is_not_numeric'], htmlspecialchars($data))
- );
- return false;
- }
- }
-
- try {
- $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));
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if ($num_results != 0) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_exists'], htmlspecialchars($domain))
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `created`, `modified`, `active`, `relay_all_recipients`)
- VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, 'virtual', :backupmx, :created, :modified, :active, :relay_all_recipients)");
- $stmt->execute(array(
- ':domain' => $domain,
- ':description' => $description,
- ':aliases' => $aliases,
- ':mailboxes' => $mailboxes,
- ':maxquota' => $maxquota,
- ':quota' => $quota,
- ':backupmx' => $backupmx,
- ':active' => $active,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':relay_all_recipients' => $relay_all_recipients
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_added'], htmlspecialchars($domain))
- );
- }
- catch (PDOException $e) {
- mailbox_delete_domain(array('domain' => $domain));
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-}
-function mailbox_add_alias($postarray) {
- // Array elements
- // address string (separated by " ", "," ";" "\n") - email address or domain
- // goto string (separated by " ", "," ";" "\n")
- // active int
- global $lang;
- global $pdo;
- $addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['address']));
- $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto']));
- isset($postarray['active']) ? $active = '1' : $active = '0';
- if (empty($addresses[0])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_empty'])
- );
- return false;
- }
-
- if (empty($gotos[0])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['goto_empty'])
- );
- return false;
- }
-
- foreach ($addresses as $address) {
- if (empty($address)) {
- continue;
- }
-
- $domain = idn_to_ascii(substr(strstr($address, '@'), 1));
- $local_part = strstr($address, '@', true);
- $address = $local_part.'@'.$domain;
-
- try {
- $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',
- 'msg' => sprintf($lang['danger']['domain_not_found'], $domain)
- );
- return false;
- }
-
- $stmt = $pdo->prepare("SELECT `address` FROM `alias`
- 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',
- 'msg' => sprintf($lang['danger']['is_alias_or_mailbox'], htmlspecialchars($address))
- );
- return false;
- }
-
- $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',
- 'msg' => sprintf($lang['danger']['is_spam_alias'], htmlspecialchars($address))
- );
- return false;
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_invalid'])
- );
- return false;
- }
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- foreach ($gotos as &$goto) {
- if (empty($goto)) {
- continue;
- }
-
- $goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1));
- $goto_local_part = strstr($goto, '@', true);
- $goto = $goto_local_part.'@'.$goto_domain;
-
- if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['goto_invalid'])
- );
- return false;
- }
- if ($goto == $address) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_goto_identical'])
- );
- return false;
- }
- }
-
- $gotos = array_filter($gotos);
- $goto = implode(",", $gotos);
-
- try {
- $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`)
- VALUES (:address, :goto, :domain, :created, :modified, :active)");
-
- if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
- $stmt->execute(array(
- ':address' => '@'.$domain,
- ':goto' => $goto,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- else {
- $stmt->execute(array(
- ':address' => $address,
- ':goto' => $goto,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['alias_added'])
- );
- }
- catch (PDOException $e) {
- mailbox_delete_alias(array('address' => $address));
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['alias_added'])
- );
-}
-function mailbox_add_alias_domain($postarray) {
- // Array elements
- // active int
- // alias_domain string
- // target_domain string
- global $lang;
- global $pdo;
- isset($postarray['active']) ? $active = '1' : $active = '0';
- $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain'])));
- $target_domain = idn_to_ascii(strtolower(trim($postarray['target_domain'])));
-
- if (!is_valid_domain_name($alias_domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
- );
- return false;
- }
-
- if (!is_valid_domain_name($target_domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['target_domain_invalid'])
- );
- return false;
- }
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- if ($alias_domain == $target_domain) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
- );
- return false;
- }
-
- try {
- $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',
- 'msg' => sprintf($lang['danger']['targetd_not_found'])
- );
- return false;
- }
-
- $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain
- UNION
- SELECT `alias_domain` FROM `alias_domain` WHERE `alias_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',
- 'msg' => sprintf($lang['danger']['aliasd_exists'])
- );
- return false;
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `created`, `modified`, `active`)
- VALUES (:alias_domain, :target_domain, :created, :modified, :active)");
- $stmt->execute(array(
- ':alias_domain' => $alias_domain,
- ':target_domain' => $target_domain,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['aliasd_added'], htmlspecialchars($alias_domain))
- );
- }
- catch (PDOException $e) {
- mailbox_delete_alias_domain(array('alias_domain' => $alias_domain));
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-}
-function mailbox_add_mailbox($postarray) {
- // Array elements
- // active int
- // local_part string
- // domain string
- // name string (username if empty)
- // password string
- // password2 string
- // quota int (MiB)
- // active int
-
- global $pdo;
- global $lang;
- $local_part = strtolower(trim($postarray['local_part']));
- $domain = idn_to_ascii(strtolower(trim($postarray['domain'])));
- $username = $local_part . '@' . $domain;
- if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_invalid'])
- );
- return false;
- }
- if (empty($postarray['local_part'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_invalid'])
- );
- return false;
- }
- $password = $postarray['password'];
- $password2 = $postarray['password2'];
- $name = $postarray['name'];
- $quota_m = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT);
-
- if (empty($name)) {
- $name = $local_part;
- }
-
- isset($postarray['active']) ? $active = '1' : $active = '0';
-
- $quota_b = ($quota_m * 1048576);
- $maildir = $domain."/".$local_part."/";
-
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $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 `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',
- 'msg' => sprintf($lang['danger']['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',
- 'msg' => sprintf($lang['danger']['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',
- 'msg' => sprintf($lang['danger']['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',
- 'msg' => sprintf($lang['danger']['domain_not_found'], $domain)
- );
- return false;
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if (!is_numeric($quota_m) || $quota_m == "0") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'])
- );
- return false;
- }
-
- if (!empty($password) && !empty($password2)) {
- if ($password != $password2) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_mismatch'])
- );
- return false;
- }
- $password_hashed = hash_password($password);
- }
- else {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_empty'])
- );
- return false;
- }
-
- if ($MailboxData['count'] >= $DomainData['mailboxes']) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['max_mailbox_exceeded'], $MailboxData['count'], $DomainData['mailboxes'])
- );
- return false;
- }
-
- if ($quota_m > $DomainData['maxquota']) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['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',
- 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m)
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `created`, `modified`, `active`)
- VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :created, :modified, :active)");
- $stmt->execute(array(
- ':username' => $username,
- ':password_hashed' => $password_hashed,
- ':name' => $name,
- ':maildir' => $maildir,
- ':quota_b' => $quota_b,
- ':local_part' => $local_part,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
-
- $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
- VALUES (:username, '0', '0')");
- $stmt->execute(array(':username' => $username));
-
- $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `created`, `modified`, `active`)
- VALUES (:username1, :username2, :domain, :created, :modified, :active)");
- $stmt->execute(array(
- ':username1' => $username,
- ':username2' => $username,
- ':domain' => $domain,
- ':created' => date('Y-m-d H:i:s'),
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
-
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['mailbox_added'], htmlspecialchars($username))
- );
- }
- catch (PDOException $e) {
- mailbox_delete_mailbox(array('address' => $username));
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-}
-function mailbox_edit_alias_domain($postarray) {
- // Array elements
- // active int
- // alias_domain_now string
- // alias_domain string
- global $lang;
- global $pdo;
- isset($postarray['active']) ? $active = '1' : $active = '0';
- $alias_domain = idn_to_ascii(strtolower(trim($postarray['alias_domain'])));
- $alias_domain_now = strtolower(trim($postarray['alias_domain_now']));
- if (!is_valid_domain_name($alias_domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
- );
- return false;
- }
-
- if (!is_valid_domain_name($alias_domain_now)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_domain_invalid'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
- WHERE `alias_domain`= :alias_domain_now");
- $stmt->execute(array(':alias_domain_now' => $alias_domain_now));
- $DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
- WHERE `target_domain`= :alias_domain");
- $stmt->execute(array(':alias_domain' => $alias_domain));
- $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if ($num_results != 0) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['aliasd_targetd_identical'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("UPDATE `alias_domain` SET
- `alias_domain` = :alias_domain,
- `active` = :active,
- `modified` = :modified,
- WHERE `alias_domain` = :alias_domain_now");
- $stmt->execute(array(
- ':alias_domain' => $alias_domain,
- ':modified' => date('Y-m-d H:i:s'),
- ':alias_domain_now' => $alias_domain_now,
- ':active' => $active
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['aliasd_modified'], htmlspecialchars($alias_domain))
- );
-}
-function mailbox_edit_alias($postarray) {
- // Array elements
- // address string
- // goto string (separated by " ", "," ";" "\n") - email address or domain
- // active int
- global $lang;
- global $pdo;
- $address = $postarray['address'];
- $domain = idn_to_ascii(substr(strstr($address, '@'), 1));
- $local_part = strstr($address, '@', true);
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (empty($postarray['goto'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['goto_empty'])
- );
- return false;
- }
- $gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $postarray['goto']));
- foreach ($gotos as &$goto) {
- if (empty($goto)) {
- continue;
- }
- if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' =>sprintf($lang['danger']['goto_invalid'])
- );
- return false;
- }
- if ($goto == $address) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_goto_identical'])
- );
- return false;
- }
- }
- $gotos = array_filter($gotos);
- $goto = implode(",", $gotos);
- isset($postarray['active']) ? $active = '1' : $active = '0';
- if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['alias_invalid'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("UPDATE `alias` SET
- `goto` = :goto,
- `active`= :active,
- `modified` = :modified,
- WHERE `address` = :address");
- $stmt->execute(array(
- ':goto' => $goto,
- ':active' => $active,
- ':address' => $address,
- ':modified' => date('Y-m-d H:i:s'),
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['alias_modified'], htmlspecialchars($address))
- );
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-}
-function mailbox_edit_domain($postarray) {
- // Array elements
- // domain string
- // description string
- // active int
- // relay_all_recipients int
- // backupmx int
- // aliases float
- // mailboxes float
- // maxquota float
- // quota float (Byte)
- // active int
-
- global $lang;
- global $pdo;
-
- $domain = idn_to_ascii($postarray['domain']);
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
-
- if ($_SESSION['mailcow_cc_role'] == "domainadmin" && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $description = $postarray['description'];
- isset($postarray['active']) ? $active = '1' : $active = '0';
- try {
- $stmt = $pdo->prepare("UPDATE `domain` SET
- `modified`= :modified,
- `description` = :description
- WHERE `domain` = :domain");
- $stmt->execute(array(
- ':modified' => date('Y-m-d H:i:s'),
- ':description' => $description,
- ':domain' => $domain
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain))
- );
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- elseif ($_SESSION['mailcow_cc_role'] == "admin") {
- $description = $postarray['description'];
- isset($postarray['active']) ? $active = '1' : $active = '0';
- $aliases = filter_var($postarray['aliases'], FILTER_SANITIZE_NUMBER_FLOAT);
- $mailboxes = filter_var($postarray['mailboxes'], FILTER_SANITIZE_NUMBER_FLOAT);
- $maxquota = filter_var($postarray['maxquota'], FILTER_SANITIZE_NUMBER_FLOAT);
- $quota = filter_var($postarray['quota'], FILTER_SANITIZE_NUMBER_FLOAT);
- isset($postarray['relay_all_recipients']) ? $relay_all_recipients = '1' : $relay_all_recipients = '0';
- isset($postarray['backupmx']) ? $backupmx = '1' : $backupmx = '0';
- isset($postarray['relay_all_recipients']) ? $backupmx = '1' : true;
- try {
- // GET MAILBOX DATA
- $stmt = $pdo->prepare("SELECT
- COUNT(*) AS count,
- MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `maxquota`,
- COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota`
- FROM `mailbox`
- WHERE domain= :domain");
- $stmt->execute(array(':domain' => $domain));
- $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
- // GET ALIAS DATA
- $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);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if ($maxquota > $quota) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_quota_exceeds_domain_quota'])
- );
- return false;
- }
-
- if ($MailboxData['maxquota'] > $maxquota) {
- echo $MailboxData['maxquota'];
- die();
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['max_quota_in_use'], $MailboxData['maxquota'])
- );
- return false;
- }
-
- if ($MailboxData['quota'] > $quota) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_quota_m_in_use'], $MailboxData['quota'])
- );
- return false;
- }
-
- if ($MailboxData['count'] > $mailboxes) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailboxes_in_use'], $MailboxData['count'])
- );
- return false;
- }
-
- if ($AliasData['count'] > $aliases) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['aliases_in_use'], $AliasData['count'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("UPDATE `domain` SET
- `modified`= :modified,
- `relay_all_recipients` = :relay_all_recipients,
- `backupmx` = :backupmx,
- `active` = :active,
- `quota` = :quota,
- `maxquota` = :maxquota,
- `modified` = :modified,
- `mailboxes` = :mailboxes,
- `aliases` = :aliases,
- `description` = :description
- WHERE `domain` = :domain");
- $stmt->execute(array(
- ':relay_all_recipients' => $relay_all_recipients,
- ':backupmx' => $backupmx,
- ':active' => $active,
- ':quota' => $quota,
- ':maxquota' => $maxquota,
- ':modified' => date('Y-m-d H:i:s'),
- ':mailboxes' => $mailboxes,
- ':aliases' => $aliases,
- ':modified' => date('Y-m-d H:i:s'),
- ':description' => $description,
- ':domain' => $domain
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars($domain))
- );
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
-}
-function mailbox_edit_mailbox($postarray) {
- global $lang;
- global $pdo;
- isset($postarray['active']) ? $active = '1' : $active = '0';
- if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['username_invalid'])
- );
- return false;
- }
- $quota_m = $postarray['quota'];
- $quota_b = $quota_m*1048576;
- $username = $postarray['username'];
- $name = $postarray['name'];
- $password = $postarray['password'];
- $password2 = $postarray['password2'];
-
- try {
- $stmt = $pdo->prepare("SELECT `domain`
- FROM `mailbox`
- WHERE username = :username");
- $stmt->execute(array(':username' => $username));
- $MailboxData1 = $stmt->fetch(PDO::FETCH_ASSOC);
-
- $stmt = $pdo->prepare("SELECT
- COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_now`
- FROM `mailbox`
- WHERE `username` = :username");
- $stmt->execute(array(':username' => $username));
- $MailboxData2 = $stmt->fetch(PDO::FETCH_ASSOC);
-
- $stmt = $pdo->prepare("SELECT
- COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota_m_in_use`
- FROM `mailbox`
- WHERE `domain` = :domain");
- $stmt->execute(array(':domain' => $MailboxData1['domain']));
- $MailboxData3 = $stmt->fetch(PDO::FETCH_ASSOC);
-
- $stmt = $pdo->prepare("SELECT `quota`, `maxquota`
- FROM `domain`
- WHERE `domain` = :domain");
- $stmt->execute(array(':domain' => $MailboxData1['domain']));
- $DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $MailboxData1['domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (!is_numeric($quota_m) || $quota_m == "0") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['quota_not_0_not_numeric'], htmlspecialchars($quota_m))
- );
- return false;
- }
- if ($quota_m > $DomainData['maxquota']) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_quota_exceeded'], $DomainData['maxquota'])
- );
- return false;
- }
- if (($MailboxData3['quota_m_in_use'] - $MailboxData2['quota_m_now'] + $quota_m) > $DomainData['quota']) {
- $quota_left_m = ($DomainData['quota'] - $MailboxData3['quota_m_in_use'] + $MailboxData2['quota_m_now']);
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['mailbox_quota_left_exceeded'], $quota_left_m)
- );
- return false;
- }
-
- // 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
- (isset($postarray['sender_acl'])) ? $sender_acl_domain_admin = $postarray['sender_acl'] : $sender_acl_domain_admin = array();
-
- if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) {
- // Check items in POST array
- foreach ($sender_acl_domain_admin as $sender_acl) {
- if (!filter_var($sender_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($sender_acl, '@'))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
- );
- return false;
- }
- if (is_valid_domain_name(ltrim($sender_acl, '@'))) {
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], ltrim($sender_acl, '@'))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
- );
- return false;
- }
- }
- if (filter_var($sender_acl, FILTER_VALIDATE_EMAIL)) {
- if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $sender_acl)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['sender_acl_invalid'])
- );
- return false;
- }
- }
- }
-
- // Merge both arrays
- $sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin);
-
- try {
- $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
- $stmt->execute(array(
- ':username' => $username
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- foreach ($sender_acl_merged as $sender_acl) {
- $domain = ltrim($sender_acl, '@');
- if (is_valid_domain_name($domain)) {
- $sender_acl = '@' . $domain;
- }
- try {
- $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
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- }
- else {
- try {
- $stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
- $stmt->execute(array(
- ':username' => $username
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- if (!empty($password) && !empty($password2)) {
- if ($password != $password2) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['password_mismatch'])
- );
- return false;
- }
- $password_hashed = hash_password($password);
- try {
- $stmt = $pdo->prepare("UPDATE `alias` SET
- `modified` = :modified,
- `active` = :active
- WHERE `address` = :address");
- $stmt->execute(array(
- ':address' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- $stmt = $pdo->prepare("UPDATE `mailbox` SET
- `modified` = :modified,
- `active` = :active,
- `password` = :password_hashed,
- `name`= :name,
- `quota` = :quota_b
- WHERE `username` = :username");
- $stmt->execute(array(
- ':modified' => date('Y-m-d H:i:s'),
- ':password_hashed' => $password_hashed,
- ':active' => $active,
- ':name' => $name,
- ':quota_b' => $quota_b,
- ':username' => $username
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
- );
- return true;
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- try {
- $stmt = $pdo->prepare("UPDATE `alias` SET
- `modified` = :modified,
- `active` = :active
- WHERE `address` = :address");
- $stmt->execute(array(
- ':address' => $username,
- ':modified' => date('Y-m-d H:i:s'),
- ':active' => $active
- ));
- $stmt = $pdo->prepare("UPDATE `mailbox` SET
- `modified` = :modified,
- `active` = :active,
- `name`= :name,
- `quota` = :quota_b
- WHERE `username` = :username");
- $stmt->execute(array(
- ':active' => $active,
- ':modified' => date('Y-m-d H:i:s'),
- ':name' => $name,
- ':quota_b' => $quota_b,
- ':username' => $username
- ));
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['mailbox_modified'], $username)
- );
- return true;
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-}
-function mailbox_get_mailboxes($domain = null) {
- global $lang;
- global $pdo;
- $mailboxes = array();
- if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- try {
- $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `domain` != 'ALL' AND `domain` = :domain");
- $stmt->execute(array(
- ':domain' => $domain,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $mailboxes[] = $row['username'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- else {
- try {
- $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` 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)) {
- $mailboxes[] = $row['username'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- return $mailboxes;
-}
-function mailbox_get_alias_domains($domain = null) {
- // Get all domains assigned to mailcow_cc_username or domain, if set
- // Domain admin needs to be active
- // Domain does not need to be active
- global $lang;
- global $pdo;
- $aliasdomains = array();
- if (isset($domain) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- elseif (isset($domain) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- try {
- $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain");
- $stmt->execute(array(
- ':domain' => $domain,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $aliasdomains[] = $row['alias_domain'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- else {
- try {
- $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'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- }
- return $aliasdomains;
-}
-function mailbox_get_aliases($domain) {
- global $lang;
- global $pdo;
- $aliases = array();
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain");
- $stmt->execute(array(
- ':domain' => $domain,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while($row = array_shift($rows)) {
- $aliases[] = $row['address'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- return $aliases;
-}
-function mailbox_get_alias_details($address) {
- global $lang;
- global $pdo;
- $aliasdata = array();
- try {
- $stmt = $pdo->prepare("SELECT
- `domain`,
- `goto`,
- `address`,
- `active` as `active_int`,
- CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
- `created`,
- `modified`
- FROM `alias`
- WHERE `address` = :address AND `address` != `goto`");
- $stmt->execute(array(
- ':address' => $address,
- ));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- $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['active_int'] = $row['active_int'];
- $aliasdata['created'] = $row['created'];
- $aliasdata['modified'] = $row['modified'];
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- return $aliasdata;
-}
-function mailbox_get_alias_domain_details($aliasdomain) {
- global $lang;
- global $pdo;
- $aliasdomaindata = array();
- try {
- $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' => $aliasdomain,
- ));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- $aliasdomaindata['alias_domain'] = $row['alias_domain'];
- $aliasdomaindata['target_domain'] = $row['target_domain'];
- $aliasdomaindata['active'] = $row['active'];
- $aliasdomaindata['active_int'] = $row['active_int'];
- $aliasdomaindata['created'] = $row['created'];
- $aliasdomaindata['modified'] = $row['modified'];
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- return $aliasdomaindata;
-}
-function mailbox_get_domains() {
- // Get all domains assigned to mailcow_cc_username
- // Domain admin needs to be active
- // Domain does not need to be active
- global $lang;
- global $pdo;
-
- try {
- $domains = array();
- $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
- WHERE (`domain` IN (
- SELECT `domain` from `domain_admins`
- WHERE (`active`='1' AND `username` = :username))
- )
- OR ('admin'= :role)
- AND `domain` != 'ALL'");
- $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'];
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- return $domains;
-}
-function mailbox_get_domain_details($domain) {
- global $lang;
- global $pdo;
-
- $domaindata = array();
- $domain = idn_to_ascii(strtolower(trim($domain)));
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("SELECT
- `domain`,
- `description`,
- `aliases`,
- `mailboxes`,
- `maxquota`,
- `quota`,
- `relay_all_recipients` as `relay_all_recipients_int`,
- `backupmx` as `backupmx_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 `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`,
- CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
- FROM `domain` WHERE `domain`= :domain");
- $stmt->execute(array(
- ':domain' => $domain,
- ));
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
- $stmt = $pdo->prepare("SELECT COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `domain` = :domain");
- $stmt->execute(array(':domain' => $row['domain']));
- $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
-
- $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['quota_used_in_domain'] = $MailboxDataDomain['in_use'];
- $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['max_quota_for_mbox'] = $row['maxquota'] * 1048576;
- $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576;
- $domaindata['backupmx'] = $row['backupmx'];
- $domaindata['backupmx_int'] = $row['backupmx_int'];
- $domaindata['active'] = $row['active'];
- $domaindata['active_int'] = $row['active_int'];
- $domaindata['relay_all_recipients'] = $row['relay_all_recipients'];
- $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int'];
-
- $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_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);
- (isset($AliasData['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasData['alias_count'] : $domaindata['aliases_in_domain'] = "0";
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- return $domaindata;
-}
-function mailbox_get_mailbox_details($mailbox) {
- global $lang;
- global $pdo;
- $mailboxdata = array();
- try {
- $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`.`quota`,
- `quota2`.`bytes`,
- `quota2`.`messages`
- FROM `mailbox`, `quota2`, `domain`
- WHERE `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");
- $stmt->execute(array(
- ':mailbox' => $mailbox,
- ));
- $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 COUNT(*) AS `count`, COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `domain` = :domain AND `username` != :username");
- $stmt->execute(array(':domain' => $row['domain'], ':username' => $row['username']));
- $MailboxUsage = $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'];
- $mailboxdata['is_relayed'] = $row['backupmx'];
- $mailboxdata['name'] = $row['name'];
- $mailboxdata['active'] = $row['active'];
- $mailboxdata['active_int'] = $row['active_int'];
- $mailboxdata['domain'] = $row['domain'];
- $mailboxdata['quota'] = $row['quota'];
- $mailboxdata['quota_used'] = intval($row['bytes']);
- $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100);
- $mailboxdata['messages'] = $row['messages'];
- if ($mailboxdata['percent_in_use'] >= 90) {
- $mailboxdata['percent_class'] = "danger";
- }
- elseif ($mailboxdata['percent_in_use'] >= 75) {
- $mailboxdata['percent_class'] = "warning";
- }
- else {
- $mailboxdata['percent_class'] = "success";
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if (!isset($mailboxdata['domain']) ||
- (isset($mailboxdata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $mailboxdata['domain']))) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- return $mailboxdata;
-}
-function mailbox_delete_domain($postarray) {
- global $lang;
- global $pdo;
- $domain = $postarray['domain'];
- if ($_SESSION['mailcow_cc_role'] != "admin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (!is_valid_domain_name($domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- $domain = strtolower(trim($domain));
-
-
- try {
- $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
- WHERE `domain` = :domain");
- $stmt->execute(array(':domain' => $domain));
- $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- if ($num_results != 0 || !empty($num_results)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_not_empty'])
- );
- return false;
- }
-
- try {
- $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` = :domain");
- $stmt->execute(array(
- ':domain' => '%@'.$domain,
- ));
- $stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` = :domain");
- $stmt->execute(array(
- ':domain' => '%@'.$domain,
- ));
- $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain");
- $stmt->execute(array(
- ':domain' => '%@'.$domain,
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['domain_removed'], htmlspecialchars($domain))
- );
- return true;
-}
-function mailbox_delete_alias($postarray) {
- global $lang;
- global $pdo;
- $address = $postarray['address'];
- $local_part = strstr($address, '@', true);
- $domain = mailbox_get_alias_details($address)['domain'];
- try {
- $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address");
- $stmt->execute(array(':address' => $address));
- $gotos = $stmt->fetch(PDO::FETCH_ASSOC);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $goto_array = explode(',', $gotos['goto']);
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- try {
- $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `address` = :address AND `address` NOT IN (SELECT `username` FROM `mailbox`)");
- $stmt->execute(array(
- ':address' => $address
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['alias_removed'], htmlspecialchars($address))
- );
-
-}
-function mailbox_delete_alias_domain($postarray) {
- global $lang;
- global $pdo;
- if (!is_valid_domain_name($postarray['alias_domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['domain_invalid'])
- );
- return false;
- }
- $alias_domain = $postarray['alias_domain'];
- try {
- $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);
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
-
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $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,
- ));
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['alias_domain_removed'], htmlspecialchars($alias_domain))
- );
-}
-function mailbox_delete_mailbox($postarray) {
- global $lang;
- global $pdo;
- $username = $postarray['username'];
- $domain = mailbox_get_mailbox_details($username)['domain'];
- if (!filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
- if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- return false;
- }
-
- try {
- $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :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 `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
- ));
- $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("SELECT `address`, `goto` FROM `alias`
- WHERE `goto` LIKE :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,
- `modified` = :modified,
- WHERE `address` = :address");
- $stmt->execute(array(
- ':goto' => $gotos_rebuild,
- ':modified' => date('Y-m-d H:i:s'),
- ':address' => $gotos['address']
- ));
- }
- }
- catch (PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- $_SESSION['return'] = array(
- 'type' => 'success',
- 'msg' => sprintf($lang['success']['mailbox_removed'], htmlspecialchars($username))
- );
-}
-function mailbox_get_sender_acl_handles($mailbox) {
- global $pdo;
- global $lang;
- if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => sprintf($lang['danger']['access_denied'])
- );
- 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();
-
- try {
- $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :goto AND `address` NOT LIKE '@%'");
- $stmt->execute(array(':goto' => $mailbox));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while ($row = array_shift($rows)) {
- $data['fixed_sender_aliases'][] = $row['address'];
- }
-
- // 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 `send_as` LIKE '@%'");
- $stmt->execute(array(':logged_in_as' => $mailbox));
- $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;
- }
- }
-
- $stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `send_as` NOT LIKE '@%'");
- $stmt->execute(array(':logged_in_as' => $mailbox));
- $address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while ($address_row = array_shift($address_rows)) {
- if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasMailboxObjectAccess($_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) && hasMailboxObjectAccess($_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_as
- AND `send_as` LIKE '@%')");
- $stmt->execute(array(
- ':logged_in_as' => $mailbox,
- ));
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
- while ($row = array_shift($rows)) {
- if (is_valid_domain_name($row['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) {
- $data['sender_acl_domains']['selectable'][] = $row['domain'];
- }
- }
-
- $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 `send_as` NOT LIKE '@%')");
- $stmt->execute(array(
- ':logged_in_as' => $mailbox,
- ':goto' => $mailbox
- ));
- while ($row = array_shift($rows)) {
- if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
- $data['sender_acl_addresses']['selectable'][] = $row['address'];
- }
- }
- }
- catch(PDOException $e) {
- $_SESSION['return'] = array(
- 'type' => 'danger',
- 'msg' => 'MySQL: '.$e
- );
- return false;
- }
- return $data;
-}
\ No newline at end of file
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index 4ec73e52..52f9a0f1 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -17,11 +17,21 @@ if (isset($_POST["logout"])) {
}
require_once 'inc/vars.inc.php';
-
if (file_exists('./inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php';
}
+// Yubi OTP API
+if (!empty($YUBI_API['ID']) && !empty($YUBI_API['KEY'])) {
+ require_once 'inc/lib/Yubico.php';
+ $yubi = new Auth_Yubico($YUBI_API['ID'], $YUBI_API['KEY']);
+}
+// U2F API
+require_once 'inc/lib/U2F.php';
+$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
+$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']);
+
+// PDO
$dsn = "$database_type:host=$database_host;dbname=$database_name";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
@@ -36,6 +46,7 @@ catch (PDOException $e) {
<center style='font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;'>🐮 Connection failed, database may be in warm-up state, please try again later.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center>
<?php
}
+
$_SESSION['mailcow_locale'] = strtolower(trim($DEFAULT_LANG));
setcookie('language', $DEFAULT_LANG);
if (isset($_COOKIE['language'])) {
@@ -82,4 +93,4 @@ require_once 'lang/lang.en.php';
include 'lang/lang.'.$_SESSION['mailcow_locale'].'.php';
require_once 'inc/functions.inc.php';
require_once 'inc/triggers.inc.php';
-(!isset($_SESSION['mailcow_cc_username'])) ? init_db_schema() : null;
+(!isset($_SESSION['mailcow_cc_username'])) ? init_db_schema() : null;
\ No newline at end of file
diff --git a/data/web/inc/tfa_modals.php b/data/web/inc/tfa_modals.php
new file mode 100644
index 00000000..4df14078
--- /dev/null
+++ b/data/web/inc/tfa_modals.php
@@ -0,0 +1,117 @@
+<div class="modal fade" id="YubiOTPModal" tabindex="-1" role="dialog" aria-labelledby="YubiOTPModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header"><b><?=$lang['tfa']['yubi_otp'];?></b></div>
+ <div class="modal-body">
+ <form role="form" method="post">
+ <div class="form-group">
+ <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
+ </div>
+ <div class="form-group">
+ <div class="input-group">
+ <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
+ <input type="text" name="otp_token" class="form-control" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
+ <input type="hidden" name="tfa_method" value="yubi_otp">
+ </div>
+ </div>
+ <button class="btn btn-sm btn-default" type="submit" name="set_tfa"><?=$lang['user']['save_changes'];?></button>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="U2FModal" tabindex="-1" role="dialog" aria-labelledby="U2FModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header"><b><?=$lang['tfa']['u2f'];?></b></div>
+ <div class="modal-body">
+ <form role="form" method="post" id="u2f_reg_form">
+ <div class="form-group">
+ <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
+ </div>
+ <p>Please enter your current password and use your U2F USB stick to confirm your identity.</p>
+ <div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
+ <input type="hidden" name="token" id="u2f_register_data"/>
+ <input type="hidden" name="tfa_method" value="u2f">
+ <input type="hidden" name="set_tfa"/><br/>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="modal fade" id="DisableTFAModal" tabindex="-1" role="dialog" aria-labelledby="DisableTFAModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header"><b><?=$lang['tfa']['delete_tfa'];?></b></div>
+ <div class="modal-body">
+ <form role="form" method="post">
+ <div class="input-group">
+ <input type="password" class="form-control" name="confirm_password" id="confirm_password" placeholder="<?=$lang['user']['password_now'];?>" autocomplete="off" required>
+ <span class="input-group-btn">
+ <input type="hidden" name="tfa_method" value="none">
+ <button class="btn btn-danger" type="submit" name="set_tfa"><?=$lang['tfa']['delete_tfa'];?></button>
+ </span>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<?php
+if (isset($_SESSION['pending_tfa_method'])):
+ $tfa_method = $_SESSION['pending_tfa_method'];
+?>
+<div class="modal fade" id="ConfirmTFAModal" tabindex="-1" role="dialog" aria-labelledby="ConfirmTFAModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header"><button type="button" class="close" data-dismiss="modal">×</button><b><?=$lang['tfa'][$tfa_method];?></b></div>
+ <div class="modal-body">
+ <?php
+ switch ($tfa_method) {
+ case "yubi_otp":
+ ?>
+ <form role="form" method="post">
+ <div class="form-group">
+ <div class="input-group">
+ <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
+ <input type="text" name="token" id="token" class="form-control" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
+ <input type="hidden" name="tfa_method" value="yubi_otp">
+ </div>
+ </div>
+ <button class="btn btn-sm btn-default" type="submit" name="verify_tfa_login"><?=$lang['login']['login'];?></button>
+ </form>
+ <?php
+ break;
+ case "u2f":
+ ?>
+ <form role="form" method="post" id="u2f_auth_form">
+ <p>Please use your U2F USB stick to confirm your identity now.</p>
+ <div class="alert alert-danger" style="display:none" id="u2f_return_code"></div>
+ <input type="hidden" name="token" id="u2f_auth_data"/>
+ <input type="hidden" name="tfa_method" value="u2f">
+ <input type="hidden" name="verify_tfa_login"/><br/>
+ </form>
+ <?php
+ break;
+ case "totp":
+ ?>
+ <div class="empty"></div>
+ <?php
+ break;
+ case "hotp":
+ ?>
+ <div class="empty"></div>
+ <?php
+ break;
+ }
+ ?>
+ </div>
+ </div>
+ </div>
+</div>
+<?php
+endif;
+?>
\ No newline at end of file
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
index c5e274fb..feb301fb 100644
--- a/data/web/inc/triggers.inc.php
+++ b/data/web/inc/triggers.inc.php
@@ -1,4 +1,15 @@
<?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.php");
+ }
+}
+
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"]);
@@ -17,13 +28,19 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$_SESSION['mailcow_cc_role'] = "user";
header("Location: /user.php");
}
- else {
+ 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']);
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => $lang['danger']['login_failed']
);
}
}
+
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
if (isset($_GET["duallogin"])) {
if (filter_var($_GET["duallogin"], FILTER_VALIDATE_EMAIL)) {
@@ -39,9 +56,9 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
}
}
}
-
- if (isset($_POST["set_admin_account"])) {
- set_admin_account($_POST);
+
+ if (isset($_POST["edit_admin_account"])) {
+ edit_admin_account($_POST);
}
if (isset($_POST["dkim_delete_key"])) {
dkim_delete_key($_POST);
@@ -55,9 +72,6 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
if (isset($_POST["delete_domain_admin"])) {
delete_domain_admin($_POST);
}
- if (isset($_POST["edit_domain_admin"])) {
- edit_domain_admin($_POST);
- }
}
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user") {
if (isset($_POST["edit_user_account"])) {
@@ -87,56 +101,64 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "user
if (isset($_POST["delete_syncjob"])) {
delete_syncjob($_POST);
}
- if (isset($_POST["trigger_set_time_limited_aliases"])) {
+ if (isset($_POST["set_time_limited_aliases"])) {
set_time_limited_aliases($_POST);
}
}
if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
+ if (isset($_POST["edit_domain_admin"])) {
+ edit_domain_admin($_POST);
+ }
+ if (isset($_POST["set_tfa"])) {
+ set_tfa($_POST);
+ }
if (isset($_POST["trigger_add_policy_list_item"])) {
add_policy_list_item($_POST);
}
if (isset($_POST["trigger_delete_policy_list_item"])) {
delete_policy_list_item($_POST);
}
- if (isset($_POST["trigger_mailbox_action"])) {
- switch ($_POST["trigger_mailbox_action"]) {
- case "adddomain":
- mailbox_add_domain($_POST);
- break;
- case "addalias":
- mailbox_add_alias($_POST);
- break;
- case "editalias":
- mailbox_edit_alias($_POST);
- break;
- case "addaliasdomain":
- mailbox_add_alias_domain($_POST);
- break;
- case "addmailbox":
- mailbox_add_mailbox($_POST);
- break;
- case "editdomain":
- mailbox_edit_domain($_POST);
- break;
- case "editmailbox":
- mailbox_edit_mailbox($_POST);
- break;
- case "deletedomain":
- mailbox_delete_domain($_POST);
- break;
- case "deletealias":
- mailbox_delete_alias($_POST);
- break;
- case "deletealiasdomain":
- mailbox_delete_alias_domain($_POST);
- break;
- case "editaliasdomain":
- mailbox_edit_alias_domain($_POST);
- break;
- case "deletemailbox":
- mailbox_delete_mailbox($_POST);
- break;
- }
+ if (isset($_POST["mailbox_add_domain"])) {
+ mailbox_add_domain($_POST);
+ }
+ if (isset($_POST["mailbox_add_alias"])) {
+ mailbox_add_alias($_POST);
+ }
+ if (isset($_POST["mailbox_add_alias_domain"])) {
+ mailbox_add_alias_domain($_POST);
+ }
+ if (isset($_POST["mailbox_add_mailbox"])) {
+ mailbox_add_mailbox($_POST);
+ }
+ if (isset($_POST["mailbox_add_mailbox"])) {
+ mailbox_add_mailbox($_POST);
+ }
+ if (isset($_POST["mailbox_edit_alias"])) {
+ mailbox_edit_alias($_POST);
+ }
+ if (isset($_POST["mailbox_edit_domain"])) {
+ mailbox_edit_domain($_POST);
+ }
+ if (isset($_POST["mailbox_edit_mailbox"])) {
+ mailbox_edit_mailbox($_POST);
+ }
+ if (isset($_POST["mailbox_edit_alias_domain"])) {
+ mailbox_edit_alias_domain($_POST);
+ }
+ if (isset($_POST["trigger_delete_policy_list_item"])) {
+ delete_policy_list_item($_POST);
+ }
+ if (isset($_POST["mailbox_delete_domain"])) {
+ mailbox_delete_domain($_POST);
+ }
+ if (isset($_POST["mailbox_delete_alias"])) {
+ mailbox_delete_alias($_POST);
+ }
+ if (isset($_POST["mailbox_delete_alias_domain"])) {
+ mailbox_delete_alias_domain($_POST);
+ }
+ if (isset($_POST["mailbox_delete_mailbox"])) {
+ mailbox_delete_mailbox($_POST);
}
}
?>
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 84073eb6..4570f818 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -1,5 +1,6 @@
<?php
-error_reporting(E_ERROR | E_WARNING);
+// error_reporting(E_ERROR | E_WARNING);
+error_reporting(E_ALL);
/*
PLEASE USE THE FILE "vars.local.inc.php" TO OVERWRITE SETTINGS AND MAKE THEM PERSISTENT!
@@ -33,4 +34,9 @@ $DEFAULT_LANG = "en";
// simplex, slate, spacelab, superhero, united, yeti
// See https://bootswatch.com/
$DEFAULT_THEME = "lumen";
+
+// If you want to use Yubico TFA methods, setup an ID and a key here: https://upgrade.yubico.com/getapikey/
+// Remember to override this value using vars.local.inc.php, do not change it here.
+$YUBI_API['ID'] = "";
+$YUBI_API['KEY'] = "";
?>
diff --git a/data/web/js/u2f-api.js b/data/web/js/u2f-api.js
new file mode 100644
index 00000000..0f06f50d
--- /dev/null
+++ b/data/web/js/u2f-api.js
@@ -0,0 +1,651 @@
+// Copyright 2014-2015 Google Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+/**
+ * @fileoverview The U2F api.
+ */
+
+'use strict';
+
+/** Namespace for the U2F api.
+ * @type {Object}
+ */
+var u2f = u2f || {};
+
+/**
+ * The U2F extension id
+ * @type {string}
+ * @const
+ */
+u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
+
+/**
+ * Message types for messsages to/from the extension
+ * @const
+ * @enum {string}
+ */
+u2f.MessageTypes = {
+ 'U2F_REGISTER_REQUEST': 'u2f_register_request',
+ 'U2F_SIGN_REQUEST': 'u2f_sign_request',
+ 'U2F_REGISTER_RESPONSE': 'u2f_register_response',
+ 'U2F_SIGN_RESPONSE': 'u2f_sign_response'
+};
+
+/**
+ * Response status codes
+ * @const
+ * @enum {number}
+ */
+u2f.ErrorCodes = {
+ 'OK': 0,
+ 'OTHER_ERROR': 1,
+ 'BAD_REQUEST': 2,
+ 'CONFIGURATION_UNSUPPORTED': 3,
+ 'DEVICE_INELIGIBLE': 4,
+ 'TIMEOUT': 5
+};
+
+/**
+ * A message type for registration requests
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * signRequests: Array<u2f.SignRequest>,
+ * registerRequests: ?Array<u2f.RegisterRequest>,
+ * timeoutSeconds: ?number,
+ * requestId: ?number
+ * }}
+ */
+u2f.Request;
+
+/**
+ * A message for registration responses
+ * @typedef {{
+ * type: u2f.MessageTypes,
+ * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
+ * requestId: ?number
+ * }}
+ */
+u2f.Response;
+
+/**
+ * An error object for responses
+ * @typedef {{
+ * errorCode: u2f.ErrorCodes,
+ * errorMessage: ?string
+ * }}
+ */
+u2f.Error;
+
+/**
+ * Data object for a single sign request.
+ * @typedef {{
+ * version: string,
+ * challenge: string,
+ * keyHandle: string,
+ * appId: string
+ * }}
+ */
+u2f.SignRequest;
+
+/**
+ * Data object for a sign response.
+ * @typedef {{
+ * keyHandle: string,
+ * signatureData: string,
+ * clientData: string
+ * }}
+ */
+u2f.SignResponse;
+
+/**
+ * Data object for a registration request.
+ * @typedef {{
+ * version: string,
+ * challenge: string,
+ * appId: string
+ * }}
+ */
+u2f.RegisterRequest;
+
+/**
+ * Data object for a registration response.
+ * @typedef {{
+ * registrationData: string,
+ * clientData: string
+ * }}
+ */
+u2f.RegisterResponse;
+
+
+// Low level MessagePort API support
+
+/**
+ * Sets up a MessagePort to the U2F extension using the
+ * available mechanisms.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ */
+u2f.getMessagePort = function(callback) {
+ if (typeof chrome != 'undefined' && chrome.runtime) {
+ // The actual message here does not matter, but we need to get a reply
+ // for the callback to run. Thus, send an empty signature request
+ // in order to get a failure response.
+ var msg = {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: []
+ };
+ chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
+ if (!chrome.runtime.lastError) {
+ // We are on a whitelisted origin and can talk directly
+ // with the extension.
+ u2f.getChromeRuntimePort_(callback);
+ } else {
+ // chrome.runtime was available, but we couldn't message
+ // the extension directly, use iframe
+ u2f.getIframePort_(callback);
+ }
+ });
+ } else if (u2f.isAndroidChrome_()) {
+ u2f.getAuthenticatorPort_(callback);
+ } else {
+ // chrome.runtime was not available at all, which is normal
+ // when this origin doesn't have access to any extensions.
+ u2f.getIframePort_(callback);
+ }
+};
+
+/**
+ * Detect chrome running on android based on the browser's useragent.
+ * @private
+ */
+u2f.isAndroidChrome_ = function() {
+ var userAgent = navigator.userAgent;
+ return userAgent.indexOf('Chrome') != -1 &&
+ userAgent.indexOf('Android') != -1;
+};
+
+/**
+ * Connects directly to the extension via chrome.runtime.connect
+ * @param {function(u2f.WrappedChromeRuntimePort_)} callback
+ * @private
+ */
+u2f.getChromeRuntimePort_ = function(callback) {
+ var port = chrome.runtime.connect(u2f.EXTENSION_ID,
+ {'includeTlsChannelId': true});
+ setTimeout(function() {
+ callback(new u2f.WrappedChromeRuntimePort_(port));
+ }, 0);
+};
+
+/**
+ * Return a 'port' abstraction to the Authenticator app.
+ * @param {function(u2f.WrappedAuthenticatorPort_)} callback
+ * @private
+ */
+u2f.getAuthenticatorPort_ = function(callback) {
+ setTimeout(function() {
+ callback(new u2f.WrappedAuthenticatorPort_());
+ }, 0);
+};
+
+/**
+ * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
+ * @param {Port} port
+ * @constructor
+ * @private
+ */
+u2f.WrappedChromeRuntimePort_ = function(port) {
+ this.port_ = port;
+};
+
+/**
+ * Format a return a sign request.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ =
+ function(signRequests, timeoutSeconds, reqId) {
+ return {
+ type: u2f.MessageTypes.U2F_SIGN_REQUEST,
+ signRequests: signRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ };
+
+/**
+ * Format a return a register request.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} signRequests
+ * @param {number} timeoutSeconds
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ =
+ function(signRequests, registerRequests, timeoutSeconds, reqId) {
+ return {
+ type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ signRequests: signRequests,
+ registerRequests: registerRequests,
+ timeoutSeconds: timeoutSeconds,
+ requestId: reqId
+ };
+ };
+
+/**
+ * Posts a message on the underlying channel.
+ * @param {Object} message
+ */
+u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
+ this.port_.postMessage(message);
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface. Works only for the
+ * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
+ function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message' || name == 'onmessage') {
+ this.port_.onMessage.addListener(function(message) {
+ // Emulate a minimal MessageEvent object
+ handler({'data': message});
+ });
+ } else {
+ console.error('WrappedChromeRuntimePort only supports onMessage');
+ }
+ };
+
+/**
+ * Wrap the Authenticator app with a MessagePort interface.
+ * @constructor
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_ = function() {
+ this.requestId_ = -1;
+ this.requestObject_ = null;
+}
+
+/**
+ * Launch the Authenticator intent.
+ * @param {Object} message
+ */
+u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
+ var intentLocation = /** @type {string} */ (message);
+ document.location = intentLocation;
+};
+
+/**
+ * Emulates the HTML 5 addEventListener interface.
+ * @param {string} eventName
+ * @param {function({data: Object})} handler
+ */
+u2f.WrappedAuthenticatorPort_.prototype.addEventListener =
+ function(eventName, handler) {
+ var name = eventName.toLowerCase();
+ if (name == 'message') {
+ var self = this;
+ /* Register a callback to that executes when
+ * chrome injects the response. */
+ window.addEventListener(
+ 'message', self.onRequestUpdate_.bind(self, handler), false);
+ } else {
+ console.error('WrappedAuthenticatorPort only supports message');
+ }
+ };
+
+/**
+ * Callback invoked when a response is received from the Authenticator.
+ * @param function({data: Object}) callback
+ * @param {Object} message message Object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
+ function(callback, message) {
+ var messageObject = JSON.parse(message.data);
+ var intentUrl = messageObject['intentURL'];
+
+ var errorCode = messageObject['errorCode'];
+ var responseObject = null;
+ if (messageObject.hasOwnProperty('data')) {
+ responseObject = /** @type {Object} */ (
+ JSON.parse(messageObject['data']));
+ responseObject['requestId'] = this.requestId_;
+ }
+
+ /* Sign responses from the authenticator do not conform to U2F,
+ * convert to U2F here. */
+ responseObject = this.doResponseFixups_(responseObject);
+ callback({'data': responseObject});
+ };
+
+/**
+ * Fixup the response provided by the Authenticator to conform with
+ * the U2F spec.
+ * @param {Object} responseData
+ * @return {Object} the U2F compliant response object
+ */
+u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ =
+ function(responseObject) {
+ if (responseObject.hasOwnProperty('responseData')) {
+ return responseObject;
+ } else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) {
+ // Only sign responses require fixups. If this is not a response
+ // to a sign request, then an internal error has occurred.
+ return {
+ 'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE,
+ 'responseData': {
+ 'errorCode': u2f.ErrorCodes.OTHER_ERROR,
+ 'errorMessage': 'Internal error: invalid response from Authenticator'
+ }
+ };
+ }
+
+ /* Non-conformant sign response, do fixups. */
+ var encodedChallengeObject = responseObject['challenge'];
+ if (typeof encodedChallengeObject !== 'undefined') {
+ var challengeObject = JSON.parse(atob(encodedChallengeObject));
+ var serverChallenge = challengeObject['challenge'];
+ var challengesList = this.requestObject_['signData'];
+ var requestChallengeObject = null;
+ for (var i = 0; i < challengesList.length; i++) {
+ var challengeObject = challengesList[i];
+ if (challengeObject['keyHandle'] == responseObject['keyHandle']) {
+ requestChallengeObject = challengeObject;
+ break;
+ }
+ }
+ }
+ var responseData = {
+ 'errorCode': responseObject['resultCode'],
+ 'keyHandle': responseObject['keyHandle'],
+ 'signatureData': responseObject['signature'],
+ 'clientData': encodedChallengeObject
+ };
+ return {
+ 'type': u2f.MessageTypes.U2F_SIGN_RESPONSE,
+ 'responseData': responseData,
+ 'requestId': responseObject['requestId']
+ }
+ };
+
+/**
+ * Base URL for intents to Authenticator.
+ * @const
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
+ 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
+
+/**
+ * Format a return a sign request.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {number} timeoutSeconds (ignored for now)
+ * @param {number} reqId
+ * @return {string}
+ */
+u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ =
+ function(signRequests, timeoutSeconds, reqId) {
+ if (!signRequests || signRequests.length == 0) {
+ return null;
+ }
+ /* TODO(fixme): stash away requestId, as the authenticator app does
+ * not return it for sign responses. */
+ this.requestId_ = reqId;
+ /* TODO(fixme): stash away the signRequests, to deal with the legacy
+ * response format returned by the Authenticator app. */
+ this.requestObject_ = {
+ 'type': u2f.MessageTypes.U2F_SIGN_REQUEST,
+ 'signData': signRequests,
+ 'requestId': reqId,
+ 'timeout': timeoutSeconds
+ };
+
+ var appId = signRequests[0]['appId'];
+ var intentUrl =
+ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+ ';S.appId=' + encodeURIComponent(appId) +
+ ';S.eventId=' + reqId +
+ ';S.challenges=' +
+ encodeURIComponent(
+ JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end';
+ return intentUrl;
+ };
+
+/**
+ * Get the browser data objects from the challenge list
+ * @param {Array} challenges list of challenges
+ * @return {Array} list of browser data objects
+ * @private
+ */
+u2f.WrappedAuthenticatorPort_
+ .prototype.getBrowserDataList_ = function(challenges) {
+ return challenges
+ .map(function(challenge) {
+ var browserData = {
+ 'typ': 'navigator.id.getAssertion',
+ 'challenge': challenge['challenge']
+ };
+ var challengeObject = {
+ 'challenge' : browserData,
+ 'keyHandle' : challenge['keyHandle']
+ };
+ return challengeObject;
+ });
+};
+
+/**
+ * Format a return a register request.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {Array<u2f.RegisterRequest>} enrollChallenges
+ * @param {number} timeoutSeconds (ignored for now)
+ * @param {number} reqId
+ * @return {Object}
+ */
+u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ =
+ function(signRequests, enrollChallenges, timeoutSeconds, reqId) {
+ if (!enrollChallenges || enrollChallenges.length == 0) {
+ return null;
+ }
+ // Assume the appId is the same for all enroll challenges.
+ var appId = enrollChallenges[0]['appId'];
+ var registerRequests = [];
+ for (var i = 0; i < enrollChallenges.length; i++) {
+ var registerRequest = {
+ 'challenge': enrollChallenges[i]['challenge'],
+ 'version': enrollChallenges[i]['version']
+ };
+ if (enrollChallenges[i]['appId'] != appId) {
+ // Only include the appId when it differs from the first appId.
+ registerRequest['appId'] = enrollChallenges[i]['appId'];
+ }
+ registerRequests.push(registerRequest);
+ }
+ var registeredKeys = [];
+ if (signRequests) {
+ for (i = 0; i < signRequests.length; i++) {
+ var key = {
+ 'keyHandle': signRequests[i]['keyHandle'],
+ 'version': signRequests[i]['version']
+ };
+ // Only include the appId when it differs from the appId that's
+ // being registered now.
+ if (signRequests[i]['appId'] != appId) {
+ key['appId'] = signRequests[i]['appId'];
+ }
+ registeredKeys.push(key);
+ }
+ }
+ var request = {
+ 'type': u2f.MessageTypes.U2F_REGISTER_REQUEST,
+ 'appId': appId,
+ 'registerRequests': registerRequests,
+ 'registeredKeys': registeredKeys,
+ 'requestId': reqId,
+ 'timeoutSeconds': timeoutSeconds
+ };
+ var intentUrl =
+ u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
+ ';S.request=' + encodeURIComponent(JSON.stringify(request)) +
+ ';end';
+ /* TODO(fixme): stash away requestId, this is is not necessary for
+ * register requests, but here to keep parity with sign.
+ */
+ this.requestId_ = reqId;
+ return intentUrl;
+ };
+
+
+/**
+ * Sets up an embedded trampoline iframe, sourced from the extension.
+ * @param {function(MessagePort)} callback
+ * @private
+ */
+u2f.getIframePort_ = function(callback) {
+ // Create the iframe
+ var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
+ var iframe = document.createElement('iframe');
+ iframe.src = iframeOrigin + '/u2f-comms.html';
+ iframe.setAttribute('style', 'display:none');
+ document.body.appendChild(iframe);
+
+ var channel = new MessageChannel();
+ var ready = function(message) {
+ if (message.data == 'ready') {
+ channel.port1.removeEventListener('message', ready);
+ callback(channel.port1);
+ } else {
+ console.error('First event on iframe port was not "ready"');
+ }
+ };
+ channel.port1.addEventListener('message', ready);
+ channel.port1.start();
+
+ iframe.addEventListener('load', function() {
+ // Deliver the port to the iframe and initialize
+ iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
+ });
+};
+
+
+// High-level JS API
+
+/**
+ * Default extension response timeout in seconds.
+ * @const
+ */
+u2f.EXTENSION_TIMEOUT_SEC = 30;
+
+/**
+ * A singleton instance for a MessagePort to the extension.
+ * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
+ * @private
+ */
+u2f.port_ = null;
+
+/**
+ * Callbacks waiting for a port
+ * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
+ * @private
+ */
+u2f.waitingForPort_ = [];
+
+/**
+ * A counter for requestIds.
+ * @type {number}
+ * @private
+ */
+u2f.reqCounter_ = 0;
+
+/**
+ * A map from requestIds to client callbacks
+ * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
+ * |function((u2f.Error|u2f.SignResponse)))>}
+ * @private
+ */
+u2f.callbackMap_ = {};
+
+/**
+ * Creates or retrieves the MessagePort singleton to use.
+ * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
+ * @private
+ */
+u2f.getPortSingleton_ = function(callback) {
+ if (u2f.port_) {
+ callback(u2f.port_);
+ } else {
+ if (u2f.waitingForPort_.length == 0) {
+ u2f.getMessagePort(function(port) {
+ u2f.port_ = port;
+ u2f.port_.addEventListener('message',
+ /** @type {function(Event)} */ (u2f.responseHandler_));
+
+ // Careful, here be async callbacks. Maybe.
+ while (u2f.waitingForPort_.length)
+ u2f.waitingForPort_.shift()(u2f.port_);
+ });
+ }
+ u2f.waitingForPort_.push(callback);
+ }
+};
+
+/**
+ * Handles response messages from the extension.
+ * @param {MessageEvent.<u2f.Response>} message
+ * @private
+ */
+u2f.responseHandler_ = function(message) {
+ var response = message.data;
+ var reqId = response['requestId'];
+ if (!reqId || !u2f.callbackMap_[reqId]) {
+ console.error('Unknown or missing requestId in response.');
+ return;
+ }
+ var cb = u2f.callbackMap_[reqId];
+ delete u2f.callbackMap_[reqId];
+ cb(response['responseData']);
+};
+
+/**
+ * Dispatches an array of sign requests to available U2F tokens.
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {function((u2f.Error|u2f.SignResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.sign = function(signRequests, callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
+
+/**
+ * Dispatches register requests to available U2F tokens. An array of sign
+ * requests identifies already registered tokens.
+ * @param {Array<u2f.RegisterRequest>} registerRequests
+ * @param {Array<u2f.SignRequest>} signRequests
+ * @param {function((u2f.Error|u2f.RegisterResponse))} callback
+ * @param {number=} opt_timeoutSeconds
+ */
+u2f.register = function(registerRequests, signRequests,
+ callback, opt_timeoutSeconds) {
+ u2f.getPortSingleton_(function(port) {
+ var reqId = ++u2f.reqCounter_;
+ u2f.callbackMap_[reqId] = callback;
+ var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
+ opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
+ var req = port.formatRegisterRequest_(
+ signRequests, registerRequests, timeoutSeconds, reqId);
+ port.postMessage(req);
+ });
+};
\ No newline at end of file
diff --git a/data/web/js/user.js b/data/web/js/user.js
index adde2f3a..8d9450be 100644
--- a/data/web/js/user.js
+++ b/data/web/js/user.js
@@ -15,9 +15,9 @@ $(document).ready(function() {
}
});
// Show generate button after time selection
- $('#trigger_set_time_limited_aliases').hide();
+ $('#generate_tla').hide();
$('#validity').change(function(){
- $('#trigger_set_time_limited_aliases').show();
+ $('#generate_tla').show();
});
// Init Bootstrap Switch
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 48849acf..d404b0bd 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -1,7 +1,7 @@
<?php
require_once 'inc/prerequisites.inc.php';
-error_reporting(0);
-if (isset($_SESSION['mailcow_cc_role'])) {
+error_reporting(E_ALL);
+if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
if ($_GET['action'] && $_GET['object']) {
$action = $_GET['action'];
$object = $_GET['object'];
@@ -24,6 +24,31 @@ if (isset($_SESSION['mailcow_cc_role'])) {
echo json_encode(mailbox_get_domain_details($object), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
break;
+ case "get_u2f_reg_challenge":
+ if (
+ ($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin")
+ &&
+ ($_SESSION["mailcow_cc_username"] == $object)
+ ) {
+ $data = $u2f->getRegisterData(get_u2f_registrations($object));
+ list($req, $sigs) = $data;
+ $_SESSION['regReq'] = json_encode($req);
+ echo 'var req = ' . json_encode($req) . '; var sigs = ' . json_encode($sigs) . ';';
+ }
+ else {
+ echo '{}';
+ }
+ break;
+ case "get_u2f_auth_challenge":
+ if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
+ $reqs = json_encode($u2f->getAuthenticateData(get_u2f_registrations($object)));
+ $_SESSION['authReq'] = $reqs;
+ echo 'var req = ' . $reqs . ';';
+ }
+ else {
+ echo '{}';
+ }
+ break;
default:
echo '{}';
break;
diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php
index a8197f62..5a854fea 100644
--- a/data/web/lang/lang.de.php
+++ b/data/web/lang/lang.de.php
@@ -94,10 +94,10 @@ $lang['user']['user_settings'] = 'Benutzereinstellungen';
$lang['user']['mailbox_settings'] = 'Mailbox-Einstellungen';
$lang['user']['mailbox_details'] = 'Mailbox-Details';
$lang['user']['change_password'] = 'Passwort ändern';
-$lang['user']['new_password'] = 'Neues Passwort:';
+$lang['user']['new_password'] = 'Neues Passwort';
$lang['user']['save_changes'] = 'Änderungen speichern';
-$lang['user']['password_now'] = 'Aktuelles Passwort (Änderungen bestätigen):';
-$lang['user']['new_password_repeat'] = 'Neues Passwort (Wiederholung):';
+$lang['user']['password_now'] = 'Aktuelles Passwort (Änderungen bestätigen)';
+$lang['user']['new_password_repeat'] = 'Neues Passwort (Wiederholung)';
$lang['user']['new_password_description'] = 'Mindestanforderung: 6 Zeichen lang, Buchstaben und Zahlen.';
$lang['user']['did_you_know'] = '<b>Wussten Sie schon?</b> Sie können Ihre E-Mail-Adresse mit Tags versehen, etwa "ich+<b>Privat</b>@example.com", um Nachrichten automatisch in einem Unterordner (Beispiel: "Privat") abzulegen.';
$lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse';
@@ -351,11 +351,19 @@ $lang['login']['login'] = 'Anmelden';
$lang['login']['previous'] = 'Vorherige Seite';
$lang['login']['delayed'] = 'Login wurde zur Sicherheit um %s Sekunde/n verzögert.';
-$lang['login']['tfa'] = 'Zwei-Faktor-Authentifizierung';
-$lang['login']['tfa_details'] = 'Bitte bestätigen Sie Ihr Einmalpasswort im folgenden Feld';
-$lang['login']['confirm'] = 'Bestätigen';
-$lang['login']['otp'] = 'Einmalpasswort';
-$lang['login']['trash_login'] = 'Login verwerfen';
+$lang['tfa']['tfa'] = "Two-Factor Authentication";
+$lang['tfa']['set_tfa'] = "Konfiguriere Two-Factor Authentication Methode";
+$lang['tfa']['yubi_otp'] = "Yubico OTP Authentifizierung";
+$lang['tfa']['u2f'] = "U2F Authentifizierung";
+$lang['tfa']['hotp'] = "HOTP Authentifizierung";
+$lang['tfa']['totp'] = "TOTP Authentifizierung";
+$lang['tfa']['none'] = "Deaktiviert";
+$lang['tfa']['delete_tfa'] = "Deaktiviere TFA";
+$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
+$lang['tfa']['confirm'] = "Bestätigen";
+$lang['tfa']['otp'] = "Einmalpasswort";
+$lang['tfa']['trash_login'] = "Login verwerfen";
+$lang['tfa']['select'] = "Bitte auswählen";
$lang['admin']['search_domain_da'] = 'Domains durchsuchen';
$lang['admin']['restrictions'] = 'Postifx Restriktionen';
diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php
index 26f8824e..33517144 100644
--- a/data/web/lang/lang.en.php
+++ b/data/web/lang/lang.en.php
@@ -89,6 +89,7 @@ $lang['warning']['spam_alias_temp_error'] = "Temporary error: Cannot add spam al
$lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses exceeded";
$lang['danger']['fetchmail_active'] = "A process is already running, please wait for it to finish.";
$lang['danger']['validity_missing'] = 'Please assign a period of validity';
+$lang['danger']['tfa_token_invalid'] = 'TFA token is invalid';
$lang['user']['on'] = "On";
$lang['user']['off'] = "Off";
$lang['user']['user_change_fn'] = "";
@@ -96,10 +97,10 @@ $lang['user']['user_settings'] = 'User settings';
$lang['user']['mailbox_settings'] = 'Mailbox settings';
$lang['user']['mailbox_details'] = 'Mailbox details';
$lang['user']['change_password'] = 'Change password';
-$lang['user']['new_password'] = 'New password:';
+$lang['user']['new_password'] = 'New password';
$lang['user']['save_changes'] = 'Save changes';
-$lang['user']['password_now'] = 'Current password (confirm changes):';
-$lang['user']['new_password_repeat'] = 'Confirmation password (repeat):';
+$lang['user']['password_now'] = 'Current password (confirm changes)';
+$lang['user']['new_password_repeat'] = 'Confirmation password (repeat)';
$lang['user']['new_password_description'] = 'Requirement: 6 characters long, letters and numbers.';
$lang['user']['did_you_know'] = '<b>Did you know?</b> You can use tags in your email address ("me+<b>privat</b>@example.com") to move messages to a folder automatically (example: "privat").';
$lang['user']['spam_aliases'] = 'Temporary email aliases';
@@ -252,7 +253,7 @@ $lang['delete']['previous'] = 'Previous page';
$lang['edit']['syncjob'] = 'Edit sync job';
$lang['edit']['save'] = 'Save changes';
-$lang['edit']['username'] = 'Save changes';
+$lang['edit']['username'] = 'Username';
$lang['edit']['hostname'] = 'Hostname';
$lang['edit']['encryption'] = 'Encryption';
$lang['edit']['maxage'] = 'Maximum age of messages in days that will be polled from remote<br /><small>(0 = ignore age)</small>';
@@ -354,11 +355,19 @@ $lang['login']['login'] = 'Login';
$lang['login']['previous'] = "Previous page";
$lang['login']['delayed'] = 'Login was delayed by %s seconds.';
-$lang['login']['tfa'] = "Two-factor authentication";
-$lang['login']['tfa_details'] = "Please confirm your one-time password in the below field";
-$lang['login']['confirm'] = "Confirm";
-$lang['login']['otp'] = "One-time password";
-$lang['login']['trash_login'] = "Trash login";
+$lang['tfa']['tfa'] = "Two-factor authentication";
+$lang['tfa']['set_tfa'] = "Set two-factor authentication method";
+$lang['tfa']['yubi_otp'] = "Yubico OTP authentication";
+$lang['tfa']['u2f'] = "U2F authentication";
+$lang['tfa']['hotp'] = "HOTP authentication";
+$lang['tfa']['totp'] = "TOTP authentication";
+$lang['tfa']['none'] = "Deaktiviert";
+$lang['tfa']['delete_tfa'] = "Disable TFA";
+$lang['tfa']['confirm_tfa'] = "Please confirm your one-time password in the below field";
+$lang['tfa']['confirm'] = "Confirm";
+$lang['tfa']['otp'] = "One-time password";
+$lang['tfa']['trash_login'] = "Trash login";
+$lang['tfa']['select'] = "Please select";
$lang['admin']['search_domain_da'] = 'Search domains';
$lang['admin']['restrictions'] = 'Postifx Restrictions';
diff --git a/data/web/lang/lang.nl.php b/data/web/lang/lang.nl.php
index 344839a5..35192ddb 100644
--- a/data/web/lang/lang.nl.php
+++ b/data/web/lang/lang.nl.php
@@ -93,10 +93,10 @@ $lang['user']['user_settings'] = 'Gebruikersinstellingen';
$lang['user']['mailbox_settings'] = 'Postvakinstellingen';
$lang['user']['mailbox_details'] = 'Postvakdetails';
$lang['user']['change_password'] = 'Verander wachtwoord';
-$lang['user']['new_password'] = 'Nieuw wachtwoord:';
+$lang['user']['new_password'] = 'Nieuw wachtwoord';
$lang['user']['save_changes'] = 'Wijzigingen opslaan';
-$lang['user']['password_now'] = 'Huidig wachtwoord (bevestig wijzigingen):';
-$lang['user']['new_password_repeat'] = 'Bevestig wachtwoord (herhalen):';
+$lang['user']['password_now'] = 'Huidig wachtwoord (bevestig wijzigingen)';
+$lang['user']['new_password_repeat'] = 'Bevestig wachtwoord (herhalen)';
$lang['user']['new_password_description'] = 'Vereisten: 6 karakters lang, letters en nummers.';
$lang['user']['did_you_know'] = '<b>Wist u dat?</b> U kunt tags in het e-mailadres gebruiken ("me+<b>prive</b>@voorbeeld.nl") om berichten automatisch naar een bijbehorende map te sturen (voorbeeld: "prive").';
$lang['user']['spam_aliases'] = 'Tijdelijk e-mailadres';
diff --git a/data/web/lang/lang.pt.php b/data/web/lang/lang.pt.php
index d2e5cf9b..f8c0e330 100644
--- a/data/web/lang/lang.pt.php
+++ b/data/web/lang/lang.pt.php
@@ -91,10 +91,10 @@ $lang['user']['user_settings'] = 'Configurações do usuário';
$lang['user']['mailbox_settings'] = 'Configrações da conta';
$lang['user']['mailbox_details'] = 'Detalhes da conta';
$lang['user']['change_password'] = 'Alterar senha';
-$lang['user']['new_password'] = 'Nova senha:';
+$lang['user']['new_password'] = 'Nova senha';
$lang['user']['save_changes'] = 'Salvar';
-$lang['user']['password_now'] = 'Senha atual (confirme a alteração):';
-$lang['user']['new_password_repeat'] = 'Confirmar senha (repetir):';
+$lang['user']['password_now'] = 'Senha atual (confirme a alteração)';
+$lang['user']['new_password_repeat'] = 'Confirmar senha (repetir)';
$lang['user']['new_password_description'] = 'Requerido: mínimo de 6 characteres com letras e números.';
$lang['user']['did_you_know'] = '<b>Você sabia?</b> Você pode usar tags no endereço de email ("conta+<b>privado</b>@example.com") para classificar as mensagens automaticamente para uma determinada pasta (exemplo: "privado").';
$lang['user']['spam_aliases'] = 'Apelidos temporários';
diff --git a/data/web/u2f_api.php b/data/web/u2f_api.php
new file mode 100644
index 00000000..634757ce
--- /dev/null
+++ b/data/web/u2f_api.php
@@ -0,0 +1,157 @@
+<?php
+require_once('inc/prerequisites.inc.php');
+$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
+
+$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
+$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']);
+
+function getRegs($username) {
+ global $pdo;
+ $sel = $pdo->prepare("select * from tfa where username = ?");
+ $sel->execute(array($username));
+ return $sel->fetchAll();
+}
+function addReg($username, $reg) {
+ global $pdo;
+ $ins = $pdo->prepare("INSERT INTO `tfa` (`username`, `keyHandle`, `publicKey`, `certificate`, `counter`) values (?, ?, ?, ?, ?)");
+ $ins->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
+}
+function updateReg($reg) {
+ global $pdo;
+ $upd = $pdo->prepare("update tfa set counter = ? where id = ?");
+ $upd->execute(array($reg->counter, $reg->id));
+}
+?>
+<html>
+<head>
+<script src="js/u2f-api.js"></script>
+<?php
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if ((empty($_POST['u2f_username'])) || (!isset($_POST['action']) && !isset($_POST['u2f_register_data']) && !isset($_POST['u2f_auth_data']))) {
+ print_r($_POST);
+ exit();
+ }
+ else {
+ $username = $_POST['u2f_username'];
+ if (isset($_POST['action'])) {
+ switch($_POST['action']) {
+ case 'register':
+ try {
+ $data = $u2f->getRegisterData(getRegs($username));
+ list($req, $sigs) = $data;
+ $_SESSION['regReq'] = json_encode($req);
+?>
+<script>
+var req = <?=json_encode($req);?>;
+var sigs = <?=json_encode($sigs);?>;
+var username = "<?=$username;?>";
+setTimeout(function() {
+ console.log("Register: ", req);
+ u2f.register([req], sigs, function(data) {
+ var form = document.getElementById('u2f_form');
+ var reg = document.getElementById('u2f_register_data');
+ var user = document.getElementById('u2f_username');
+ var status = document.getElementById('u2f_status');
+ console.log("Register callback", data);
+ if (data.errorCode && data.errorCode != 0) {
+ var div = document.getElementById('u2f_return_code');
+ div.innerHTML = 'Error code: ' + data.errorCode;
+ return;
+ }
+ reg.value = JSON.stringify(data);
+ user.value = username;
+ status.value = "1";
+ form.submit();
+ });
+}, 1000);
+</script>
+<?php
+ }
+ catch( Exception $e ) {
+ echo "U2F error: " . $e->getMessage();
+ }
+ break;
+
+ case 'authenticate':
+ try {
+ $reqs = json_encode($u2f->getAuthenticateData(getRegs($username)));
+ $_SESSION['authReq'] = $reqs;
+?>
+<script>
+var req = <?=$reqs;?>;
+var username = "<?=$username;?>";
+setTimeout(function() {
+ console.log("sign: ", req);
+ u2f.sign(req, function(data) {
+ var form = document.getElementById('u2f_form');
+ var auth = document.getElementById('u2f_auth_data');
+ var user = document.getElementById('u2f_username');
+ console.log("Authenticate callback", data);
+ auth.value = JSON.stringify(data);
+ user.value = username;
+ form.submit();
+ });
+}, 1000);
+</script>
+<?php
+ }
+ catch (Exception $e) {
+ echo "U2F error: " . $e->getMessage();
+ }
+ break;
+ }
+ }
+ if (!empty($_POST['u2f_register_data'])) {
+ try {
+ $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_POST['u2f_register_data']));
+ addReg($username, $reg);
+ }
+ catch (Exception $e) {
+ echo "U2F error: " . $e->getMessage();
+ }
+ finally {
+ echo "Success";
+ $_SESSION['regReq'] = null;
+ }
+ }
+ if (!empty($_POST['u2f_auth_data'])) {
+ try {
+ $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), getRegs($username), json_decode($_POST['u2f_auth_data']));
+ updateReg($reg);
+ }
+ catch (Exception $e) {
+ echo "U2F error: " . $e->getMessage();
+ }
+ finally {
+ echo "Success";
+ $_SESSION['authReq'] = null;
+ }
+ }
+ }
+?>
+</head>
+<body>
+<div id="u2f_return_code"></div>
+<form method="POST" id="u2f_form">
+<input type="hidden" name="u2f_register_data" id="u2f_register_data"/>
+<input type="hidden" name="u2f_auth_data" id="u2f_auth_data"/>
+<input type="hidden" name="u2f_username" id="u2f_username"/><br/>
+<input type="hidden" name="u2f_status" id="u2f_status"/><br/>
+</form>
+<?php
+}
+else {
+?>
+<form method="POST" id="post_form">
+Username: <input name="u2f_username" id="u2f_username"/><br/><hr>
+Action: <br />
+<input value="register" name="action" type="radio"/> Register<br/>
+<input value="authenticate" name="action" type="radio"/> Authenticate<br/>
+<button type="submit">Submit!</button>
+ </form>
+<?php
+}
+?>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/web/user.php b/data/web/user.php
index 8fe71561..d158439d 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -1,53 +1,67 @@
<?php
require_once("inc/prerequisites.inc.php");
-if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+
+ /*
+ / DOMAIN ADMIN
+ */
+
require_once("inc/header.inc.php");
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$username = $_SESSION['mailcow_cc_username'];
- $get_tls_policy = get_tls_policy($_SESSION['mailcow_cc_username']);
?>
<div class="container">
-<h3><?=$lang['user']['mailbox_settings'];?></h3>
-
-<div class="panel panel-default">
-<div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div>
-<div class="panel-body">
- <form class="form-horizontal" role="form" method="post" autocomplete="off">
- <div class="form-group">
- <div class="col-sm-offset-3 col-sm-10">
- <div class="checkbox">
- <label><input type="checkbox" name="togglePwNew" id="togglePwNew"> <?=$lang['user']['change_password'];?></label>
- </div>
+ <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>
</div>
</div>
- <div class="passFields">
- <div class="form-group">
- <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
- <div class="col-sm-5">
- <input type="password" class="form-control" name="user_new_pass" id="user_new_pass" autocomplete="off" disabled="disabled" required>
- </div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-3" for="user_new_pass2"><?=$lang['user']['new_password_repeat'];?></label>
- <div class="col-sm-5">
- <input type="password" class="form-control" name="user_new_pass2" id="user_new_pass2" disabled="disabled" autocomplete="off" required>
- <p class="help-block"><?=$lang['user']['new_password_description'];?></p>
- </div>
+ <hr>
+ <div class="row">
+ <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
+ <div class="col-md-9 col-xs-7">
+ <p><?=get_tfa()['pretty'];?></p>
</div>
- <hr>
</div>
- <div class="form-group">
- <label class="control-label col-sm-3" for="user_old_pass"><?=$lang['user']['password_now'];?></label>
- <div class="col-sm-5">
- <input type="password" class="form-control" name="user_old_pass" id="user_old_pass" autocomplete="off" required>
+ <div class="row">
+ <div class="col-md-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div>
+ <div class="col-md-9 col-xs-7">
+ <select id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
+ <option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
+ <option value="none"><?=$lang['tfa']['none'];?></option>
+ </select>
</div>
</div>
- <div class="form-group">
- <div class="col-sm-offset-3 col-sm-9">
- <button type="submit" name="edit_user_account" class="btn btn-success btn-default"><?=$lang['user']['save_changes'];?></button>
- </div>
+ </div>
+</div>
+<?php
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+
+ /*
+ / USER
+ */
+
+ require_once("inc/header.inc.php");
+ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+ $username = $_SESSION['mailcow_cc_username'];
+ $get_tls_policy = get_tls_policy($_SESSION['mailcow_cc_username']);
+?>
+<div class="container">
+<h3><?=$lang['user']['user_settings'];?></h3>
+
+<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">
+ <p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p>
</div>
- </form>
+ </div>
<hr>
<?php // Get user information about aliases
$user_get_alias_details = user_get_alias_details($username);?>
@@ -170,7 +184,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user
<option value="168">1 <?=$lang['user']['week'];?></option>
<option value="672">4 <?=$lang['user']['weeks'];?></option>
</select>
- <button type="submit" name="set_time_limited_aliases" value="generate" class="btn btn-success"><?=$lang['user']['alias_create_random'];?></button>
+ <button type="submit" name="set_time_limited_aliases" id="generate_tla" value="generate" class="btn btn-success"><?=$lang['user']['alias_create_random'];?></button>
</div>
</div>
<div class="form-group">
@@ -423,7 +437,16 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user
</div>
</div>
</div>
-<div style="margin-bottom:200px;"></div>
+
+<?php
+}
+if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "user" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
+
+ /*
+ / USER OR DOMAIN ADMIN
+ */
+
+?>
<div class="modal fade" id="logModal" tabindex="-1" role="dialog" aria-labelledby="logTextLabel">
<div class="modal-dialog" style="width:90%" role="document">
<div class="modal-content">
@@ -433,12 +456,50 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user
</div>
</div>
</div>
+
+<div style="margin-bottom:200px;"></div>
+<div class="modal fade" id="pwChangeModal" tabindex="-1" role="dialog" aria-labelledby="pwChangeModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ <form class="form-horizontal" role="form" method="post" autocomplete="off">
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_new_pass"><?=$lang['user']['new_password'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" name="user_new_pass" id="user_new_pass" autocomplete="off" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_new_pass2"><?=$lang['user']['new_password_repeat'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" name="user_new_pass2" id="user_new_pass2" autocomplete="off" required>
+ <p class="help-block"><?=$lang['user']['new_password_description'];?></p>
+ </div>
+ </div>
+ <hr>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="user_old_pass"><?=$lang['user']['password_now'];?></label>
+ <div class="col-sm-5">
+ <input type="password" class="form-control" name="user_old_pass" id="user_old_pass" autocomplete="off" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <button type="submit" name="edit_<?=($_SESSION['mailcow_cc_role'] == "domainadmin") ? "domain_admin" : "user_account";?>" class="btn btn-sm btn-success"><?=$lang['user']['change_password'];?></button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
</div> <!-- /container -->
<script src="js/sorttable.js"></script>
<script src="js/user.js"></script>
<?php
require_once("inc/footer.inc.php");
-} else {
+}
+else {
header('Location: /');
exit();
}